Jenkins XによるPreview環境構築

本記事は リクルートライフスタイル Advent Calendar 2019 3日目の記事です。

じゃらんnetを担当している永井です。

最近、案件ごとに独立して動作確認できる環境を作りたくて、Jenkins Xを検証していました。 Jenkins Xはまだ情報が少なく、一筋縄では行かなかったので本記事ではその構築過程をご紹介します。

Jenkins X

Jenkins Xは、Git/Docker/Kubernetesの利用を前提として構築・運用を可能な限り自動化してくれるCI/CDソリューションです。 jx create clusterと叩けばGKEEKSなどに標準的なKubernetesクラスタを作成してくれ、 jx installと叩けば対象のKubernetesクラスタにJenkins XをインストールしてGitHubにwebhookまで作成してくれます。 Webコンソール画面は基本必要なくなり、jxコマンド経由で各ジョブの状況も確認できます。

Jenkins Xでできることは全てJenkinsと各種プラグインを活用することで実現可能らしいのですが、 JenkinsやKubernetesまわりについて深く知らずとも幾つかのコマンドを実行していくことで適したCI/CD環境を構築できることが魅力です。

私も、普段はFlutterを推進したり、 Spring BootをGAECloud Runで動かしたりといった具合で、 素のJenkinsをKubernetesに最適化しろと言われると、正直知識が不足していて全然イメージが湧きません。

しかしJenkins Xであれば、これからご紹介していくように各技術への理解が曖昧でも(幾つかのissuesと向き合いながらではありますが)モダンなCI/CDを構築・運用できそうです。

目的

本記事では、特にJenkins Xのpreview環境に注目していきます。

異なる開発者たちが、複数の機能追加・改修を担当しており、それぞれを独立して動作確認したい、という状況はよくあることだと思います。 これまでは、動作確認したい案件ごとに都度環境構築するなどしていましたが、手間も費用も掛かって非効率です。 Kubernetesを活用すれば、リソースを効率的に使いながら、それぞれの環境を独立して動作させることができます。

Jenkins Xはその構築と運用の手助けをしてくれるということで、今回検証してみることにしました。

構築

構築にあたり、以下を前提に置いています。

  • GitHub Enterpriseとの連携(GitHubとGitHub EnterpriseではJenkins Xの設定も一部異なります)
  • GitHub Enterpriseとの通信は固定IPでなければならない

Jenkins Xは様々なKubernetes環境に対応していますが、 今回は最も対応が進んでいそうなGKEと、実際構築を予定しているEKSの2つを試しました。

Kubernetesクラスタ作成

GKEの場合

GKEで「GitHub Enterpriseとの通信は固定IPでなければならない」をクリアするためには、VPCとNATゲートウェイを用意した上で、jx create cluster gkeではなく自前でクラスタを作成する必要があります。 jx create cluster gkeを実行すると、限定公開クラスタ(private cluster)が無効な状態でクラスタが作成されるため、 NATゲートウェイのIPアドレスでGitHub Enterpriseと通信してくれません。

EKSの場合

EKSの場合は、jx create cluster eksを実行すると、実質eksctl create clusterが実行され、 public subnets3つとprivate subnets3つ、そしてIAMロールとInternet/NATゲートウェイが作成されます。 GitHub Enterpriseとの通信には、ここで作成されたNATゲートウェイのIPアドレスを使います。

このとき、jx create cluster eksではなくeksctl create cluster --managedを実行すると、最近使えるようになったEKSのManaged Node Groupsを利用できます。 ここで作成されたManaged Node Groupはpublic subnetsに作成されており、その通信はNATゲートウェイを通りません。 今回は、こうして作成されたManaged Node Groupを一度削除して、 改めてprivate subnetsを選択した新しいManaged Node Groupを作成することでNATゲートウェイを通るようにしました。 ここで、Nodes用のロールは自動作成されたIAMロール(eksctl-<cluster-name>-nodegro-NodeInstanceRole-<id>)を選択します。

jx create cluster eksを実行する場合は、そのままだとこの後説明する2通りのJenkins Xインストールのうちdeprecatedな方が実行されるので、 ここでは--skip-installationオプションを付けて実行することをオススメします。

Jenkins Xインストール

Jenkins Xのインストール方法には現在2通り存在します。

コマンド 説明
jx install 従来の方法。コマンドに各オプションを付与したり、標準入力経由で設定値を入力していく。2020/01/02にdeprecatedになる予定。
jx boot Jenkins Xの設定もgitで管理する方法。今後はなるべくこちらを利用することが推奨されている。

これを見ると「jx bootを使おう」となると思うのですが、jx installだと問題ないけれどjx bootでは躓くissuesも幾つかあるようです。 一方で、jx installで発見されたissuesは、jx bootに切り替えることを提案されて終わっていたりもします。 jx bootを試しながら、問題があればissuesやpull requestsを作成して地道に進んでいくことが建設的なのかもしれませんが、 差し当たりどちらを使っていくか悩ましいところです。

jx install

1
2
3
4
5
6
7
$ jx install --provider={gke|eks} \
  --git-provider-url=https://<github enterprise domain> \
  --no-default-environments \
  --domain=<jenkins x domain> \
  --exposecontroller-urltemplate='"{{.Service}}-{{.Namespace}}.{{.Domain}}"' \
  --urltemplate='"{{.Service}}-{{.Namespace}}.{{.Domain}}"' \
  --verbose

のように実行します。 今回欲しいのはpreview環境のみなので、--no-default-environmentsオプションを付与することでデフォルトで作成されるstaging/production環境を作成しないようにします。 --exposecontroller-urltemplate--urltemplateは、SSL証明書を*.<domain>で作成できるように指定しています(デフォルトは'"{{.Service}}.{{.Namespace}}.{{.Domain}}"')。

コマンドを実行すると、以下のような選択を求められます。

1
2
3
? Select Jenkins installation type:    [Use arrows to move, space to select, type to filter]
> Serverless Jenkins X Pipelines with Tekton
  Static Jenkins Server and Jenkinsfiles

残念ながら、前者はGitHub以外に対応していないようです。 そのため、GitHub EnterpriseやBitBucketを利用している場合は後者を選択しましょう。

しばらくするとjxing-nginx-ingress-controllerというロードバランサーが立ち上がるので、

1
2
3
$ kubectl get services -n kube-system
NAME                                  TYPE           CLUSTER-IP       EXTERNAL-IP                                                                    PORT(S)                      AGE
jxing-nginx-ingress-controller        LoadBalancer   XXX.XXX.XXX.XXX   xxxxx.ap-northeast-1.elb.amazonaws.com   80:xxxxx/TCP,443:xxxxx/TCP   9m20s

上記EXTERNAL IPを*.<domain>の向き先として設定する必要があります。

このあとは、コンソールに表示される指示に従っていけば、無事Jenkins adminユーザーのパスワードが表示され、http://jenkins-jx.<domain> でアクセスできるようになります。

HTTPSでアクセスしたい、という場合は工夫が必要です。 基本的には jx update ingress --namespaces=jx --verbose と実行すれば良いのですが、GKE/AWSともにそのまま放っておくと成功しません。 こちらにあるように、 part0-configmap.yamlを編集してkubectl applyする必要があります。

jx boot

jx installは工夫が必要ながらも何とかJenkins Xをインストールできました。jx bootはどうでしょうか。

まず、現時点でHelmはv2.15.0未満である必要があります

その上で、Jenkins Xの設定値を管理するレポジトリをenvironment-<kubernetes-cluster-name>-devの名前で用意します。 中身は、 jenkins-x/jenkins-x-boot-config の内容でpushします。

この各yamlファイルをローカルで修正し、jx bootすることでJenkins Xのインストール・更新ができます。

今回はpreview環境のみが必要なので、staging/production環境のための設定は削除します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
--- a/env/jxboot-resources/values.tmpl.yaml
+++ b/env/jxboot-resources/values.tmpl.yaml
@@ -65,17 +65,6 @@ gitops:
     dockerRegistryOrg: ""
 {{- end }}
-
-  staging:
-    repo: "{{ .Environments.staging.repository }}"
-    owner: "{{ .Environments.staging.owner | default .Requirements.cluster.environmentGitOwner }}"
-    server: ""
-
-  production:
-    repo: "{{ .Environments.production.repository }}"
-    owner: "{{ .Environments.production.owner | default .Requirements.cluster.environmentGitOwner }}"
-    server: ""
-
 storage:
   logs:
     url: "{{ .Requirements.storage.logs.url }}"

jx-requirements.ymlの各設定値はこちらを参照して設定していきましょう。 GitHub Enterpriseの場合、webhookはlighthouseにする必要があります。

また、secretStorageは現時点localでなければならないようです。 GKE/AWSともに、 secretStoragevaultにすると失敗してしまいます。

修正が終わったら、environment-<kubernetes-cluster-name>-devの直下で jx boot --verbose を実行します。

1
2
3
4
DEBUG: Applying Helm hook post-upgrade YAML via kubectl in file: /tmp/helm-template-workdir-719161591/jenkins-x/helmHooks/env/charts/jenkins-x-platform/charts/expose/templates/job.yaml
DEBUG: job.batch/expose created
DEBUG: Waiting for helm post-upgrade hook Job expose to complete before removing it

と表示されてからしばらくDEBUGログも出ない状態になりますが、気長に待てば無事インストール完了まで行けるはずです。

プロジェクト追加

新しくJenkins Xにジョブを追加したい場合は、jx create quickstartjx create springで始めることをオススメします。 既存のプロジェクトをjx importすることも可能ですが、Jenkinsfileの他に、こちらにあるようなファイル群をプロジェクト内に作成し、正しく記述する必要があります。

jx create quickstartなどでプロジェクトを作成すれば、あとはアプリケーションのソースコードを移植してDockerfileを修正することで基本的には動くようになるはずです。

ただし、EKSの場合はまたもう一工夫必要です。 jx create quickstartなどでプロジェクトを作成すると、自動的にmasterブランチのジョブが走り始めます。 すると以下のようなエラーでジョブが失敗します。

1
2
3
[ERROR] Error parsing the serverURL: https://index.docker.io/v1/,
error: docker-credential-ecr-login can only be used with Amazon Elastic Container Registry.
credentials not found in native keychain

このエラーは、こちらにあるような方法でsecretを修正すれば解消します。

jx bootでJenkins Xをインストールした場合は、environment-<kubernetes-cluster-name>-devにもpull requestが作成されているはずです。 このpull requestをマージして、再度jx bootを実行してから次のステップへ進みましょう。

Preview環境

さて、ここまででmasterブランチのジョブは通るようになり、いよいよpull requestsに対してpreview環境を立ち上げます。 jx create quickstartなどで作成したプロジェクトのJenkinsfileには、既にjx previewコマンドが記述されているはずです。 この箇所を、以下のように修正しましょう。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -17,6 +17,7 @@ pipeline {
         PREVIEW_VERSION = "0.0.0-SNAPSHOT-$BRANCH_NAME-$BUILD_NUMBER"
         PREVIEW_NAMESPACE = "$APP_NAME-$BRANCH_NAME".toLowerCase()
         HELM_RELEASE = "$PREVIEW_NAMESPACE".toLowerCase()
       }
       steps {
         container('nodejs') {
@@ -27,7 +28,9 @@ pipeline {
           sh "jx step post build --image $DOCKER_REGISTRY/$ORG/$APP_NAME:$PREVIEW_VERSION"
           dir('./charts/preview') {
             sh "make preview"
-            sh "jx preview --app $APP_NAME --dir ../.."
+            sh "jx preview --app $APP_NAME --dir ../.. --urltemplate='\"{{.Service}}-{{.Namespace}}.{{.Domain}}\"'"
           }
         }
       }

--urltemplateを追加しているのは、jx installの時と同じ理由です。

また、preview環境に対してHTTPSで通信できるように、以下を修正します。

1
2
3
4
5
6
7
8
9
10
11
--- a/charts/preview/values.yaml
+++ b/charts/preview/values.yaml
@@ -6,7 +6,7 @@ expose:
   config:
     exposer: Ingress
     http: true
-    tlsacme: false
+    tlsacme: true

 cleanup:
   Args:

こうしてpull requestを作成すると、webhook経由でジョブが走り始めます。

例えば、 jx create quickstartnode-http を選択したとします。 Preview用のブランチで、以下のように修正してみます。

1
2
3
4
5
6
7
8
9
10
11
--- a/index.html
+++ b/index.html
@@ -12,7 +12,7 @@
 <body>
        <div class="cover">
                <div class="feature">
-                       <h1>Hello Node Preview</h1>
+                       <h1>Hello Node</h1>
                        <div class="brand">
                                Jenkins <strong>X</strong>
                        </div>

このブランチのpull requestを作成して、remoteにpushしてみます。 すると、 node-xxx という名前のジョブ実行用podが立ち上がり、

1
2
3
4
5
6
7
8
9
$ kubectl get pods
NAME                                        READY   STATUS      RESTARTS   AGE
exposecontroller-59tm2                      0/1     Completed   0          57m
jenkins-845998c594-8zbg5                    1/1     Running     0          61m
jenkins-x-chartmuseum-d87cbb789-dzl4h       1/1     Running     0          61m
jenkins-x-controllerrole-8499688fb5-2f84d   1/1     Running     0          61m
jenkins-x-heapster-ff6df6848-mmb95          2/2     Running     0          61m
jenkins-x-nexus-6bc788447f-6xxgn            1/1     Running     0          61m
nodejs-b1rll                                2/2     Running     0          2m7s

完了次第、 https://<repo-name>-jx-<org-name>-<rep-name>-pr-<pr-number>.<domain>/ というようなURLが生成され、 pull requestに対してURL付きでコメントがpushされます。

pr-comment pr-preview

無事preview環境が出来上がりました! 🎉

クローズしたpull requestsに対するpreviewsは、定期的に削除してくれるようです。

1
2
3
4
5
$ kubectl get cronjobs
NAME                     SCHEDULE         SUSPEND   ACTIVE   LAST SCHEDULE   AGE
jenkins-x-gcactivities   0/30 */3 * * *   False     0        <none>          107m
jenkins-x-gcpods         0/30 */3 * * *   False     0        <none>          107m
jenkins-x-gcpreviews     0 */3 * * *      False     0        <none>          107m

まとめ

いかがでしたでしょうか。

正直まだまだ躓き所が多い印象ではありますが、調べれば何とか解決していけますし、学習・コミット機会と捉えて楽しむこともできそうです。

これを機に、一緒にissuesと向き合う仲間が増えてくれると嬉しいです。