Level 3: CI/CDパイプラインを構築¶
目的・ゴール: コンテナ化したアプリケーションのCICDを実現する¶
アプリケーションをコンテナ化したら、常にリリース可能な状態、自動でデプロイメントを出来る仕組みをつくるのが迅速な開発をするために必要になります。
そのためのCI/CDパイプラインを作成するのがこのレベルの目標です。
本ラボでは Level1, Level2 で行ったオペレーションをベースにCI/CDパイプラインを構築します。
Gitにソースがコミットされたら自動でテスト・ビルドを実現するためのツール(Jenkins)をkubernetes上へデプロイ、及び外部公開をします。 そして、Jenkinsがデプロイできたら実際にアプリケーションの変更を行い自動でデプロイするところまでを目指します。
流れ¶
- Jenkins をインストールする
- Jenkins 内部でジョブを定義する。
- あるアクションをトリガーにビルド、テストを自動実行する。
- 自動でk8sクラスタにデプロイメントできるようにする。
CI/CDパイプラインの定義¶
このラボでのCI/CDパイプラインの定義は以下を想定しています。
- テスト実行
- アプリケーションビルド
- コンテナイメージのビルド
- レジストリへコンテナイメージのpush
- k8sへアプリケーションデプロイ
Gitは共有で準備しています。
ここではJenkinsをkubernetes上にデプロイしてみましょう。 Git自体も併せてデプロイしてみたいということであればGitLabをデプロイすることをおすすめします。 GitLabを使えばコンテナのCI/CDパイプライン、構成管理、イメージレジストリを兼ねて使用することができます。
Jenkinsのデプロイ方法について¶
CI/CDパイプラインを実現するためのツールとしてJenkinsが非常に有名であることは周知の事実です。 このラボではJenkinsを使用しCI/CDを実現します。
まずは、各自Jenkinsをデプロイします。
方法としては3つ存在します。
- Helm Chartでデプロイする方法 (手軽にインストールしたい人向け)
- Level1,2と同じようにyamlファイルを作成し、デプロイする方法(仕組みをより深く知りたい人向け)
- Kubernetes用にCI/CDを提供するJenkins Xをデプロイする方法(新しい物を使いたい人向け)
今回は最初のHelmでデプロイするバージョンを記載しました。 好みのもの、挑戦したい内容に沿って選択してください。
オリジナルでyamlファイルを作成する場合は以下のサイトが参考になります。
Helmを使ってJenkinsをデプロイ¶
Helmの初期化¶
Helmを使用する事前の設定をします。 Helmの初期化、RBACの設定を実施します。
$ helm init
$ kubectl create clusterrolebinding add-on-cluster-admin --clusterrole=cluster-admin --serviceaccount=kube-system:default
Helmチャートのインストール・Jenkinsのカスタマイズ¶
今回はJenkinsを導入するにあたり環境に併せてカスタマイズを行います。 Helmは以下のURLに様々なものが公開されています。パラメータを与えることである程度カスタマイズし使用することができます。 Helm chartと同等のディレクトリにvalues.yamlというファイルが存在し、これを環境に併せて変更することでカスタマイズしデプロイできます。
今回のJenkinsのデプロイでは2つの公開方法が選択できます。
1つ目が今までのレベルと同様に Service
の type
を NodePort
として公開する方法です。これは今まで通りの疎通確認が可能です。
2つ目が Ingress
を使った公開です。IngressをJenkinsのHelmチャートを使ってデプロイするためには「Master.Ingress.Annotations」、「Master.ServiceType」を変更しデプロイします。
また、このvalues.yamlでは永続化ストレージが定義されていないため、Level2で作成したStorageClassを使用し動的にプロビジョニングをするように変更しましょう。
簡易的にデプロイをためしてみたい方は1つ目の NodePort
を使ったやり方を実施、新しい概念であるIngressを使った方法を実施したい方は2つ目を選択しましょう。
どちらの方法の場合も、以下のvalues.yamlをカスタマイズしてデプロイします。
# Default values for jenkins.
# This is a YAML-formatted file.
# Declare name/value pairs to be passed into your templates.
# name: value
## Overrides for generated resource names
# See templates/_helpers.tpl
# nameOverride:
# fullnameOverride:
Master:
Name: jenkins-master
Image: "jenkins/jenkins"
ImageTag: "lts"
ImagePullPolicy: "Always"
# ImagePullSecret: jenkins
Component: "jenkins-master"
UseSecurity: true
AdminUser: admin
# AdminPassword: <defaults to random>
Cpu: "200m"
Memory: "256Mi"
# Environment variables that get added to the init container (useful for e.g. http_proxy)
# InitContainerEnv:
# - name: http_proxy
# value: "http://192.168.64.1:3128"
# ContainerEnv:
# - name: http_proxy
# value: "http://192.168.64.1:3128"
# Set min/max heap here if needed with:
# JavaOpts: "-Xms512m -Xmx512m"
# JenkinsOpts: ""
# JenkinsUriPrefix: "/jenkins"
# Set RunAsUser to 1000 to let Jenkins run as non-root user 'jenkins' which exists in 'jenkins/jenkins' docker image.
# When setting RunAsUser to a different value than 0 also set FsGroup to the same value:
# RunAsUser: <defaults to 0>
# FsGroup: <will be omitted in deployment if RunAsUser is 0>
ServicePort: 8080
# For minikube, set this to NodePort, elsewhere use LoadBalancer
# Use ClusterIP if your setup includes ingress controller
ServiceType: LoadBalancer
# Master Service annotations
ServiceAnnotations: {}
# service.beta.kubernetes.io/aws-load-balancer-backend-protocol: https
# Used to create Ingress record (should used with ServiceType: ClusterIP)
# HostName: jenkins.cluster.local
# NodePort: <to set explicitly, choose port between 30000-32767
ContainerPort: 8080
# Enable Kubernetes Liveness and Readiness Probes
HealthProbes: true
HealthProbesTimeout: 60
SlaveListenerPort: 50000
# Kubernetes service type for the JNLP slave service
# SETTING THIS TO "LoadBalancer" IS A HUGE SECURITY RISK: https://github.com/kubernetes/charts/issues/1341
SlaveListenerServiceType: ClusterIP
SlaveListenerServiceAnnotations: {}
LoadBalancerSourceRanges:
- 0.0.0.0/0
# Optionally assign a known public LB IP
# LoadBalancerIP: 1.2.3.4
# Optionally configure a JMX port
# requires additional JavaOpts, ie
# JavaOpts: >
# -Dcom.sun.management.jmxremote.port=4000
# -Dcom.sun.management.jmxremote.authenticate=false
# -Dcom.sun.management.jmxremote.ssl=false
# JMXPort: 4000
# List of plugins to be install during Jenkins master start
InstallPlugins:
- kubernetes:1.1
- workflow-aggregator:2.5
- workflow-job:2.15
- credentials-binding:1.13
- git:3.6.4
# Used to approve a list of groovy functions in pipelines used the script-security plugin. Can be viewed under /scriptApproval
# ScriptApproval:
# - "method groovy.json.JsonSlurperClassic parseText java.lang.String"
# - "new groovy.json.JsonSlurperClassic"
# List of groovy init scripts to be executed during Jenkins master start
InitScripts:
# - |
# print 'adding global pipeline libraries, register properties, bootstrap jobs...'
# Kubernetes secret that contains a 'credentials.xml' for Jenkins
# CredentialsXmlSecret: jenkins-credentials
# Kubernetes secret that contains files to be put in the Jenkins 'secrets' directory,
# useful to manage encryption keys used for credentials.xml for instance (such as
# master.key and hudson.util.Secret)
# SecretsFilesSecret: jenkins-secrets
# Jenkins XML job configs to provision
# Jobs: |-
# test: |-
# <<xml here>>
CustomConfigMap: false
# Node labels and tolerations for pod assignment
# ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector
# ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#taints-and-tolerations-beta-feature
NodeSelector: {}
Tolerations: {}
Ingress:
Annotations:
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
TLS:
# - secretName: jenkins.cluster.local
# hosts:
# - jenkins.cluster.local
Agent:
Enabled: true
Image: jenkins/jnlp-slave
ImageTag: 3.10-1
# ImagePullSecret: jenkins
Component: "jenkins-slave"
Privileged: false
Cpu: "200m"
Memory: "256Mi"
# You may want to change this to true while testing a new image
AlwaysPullImage: false
# You can define the volumes that you want to mount for this container
# Allowed types are: ConfigMap, EmptyDir, HostPath, Nfs, Pod, Secret
# Configure the attributes as they appear in the corresponding Java class for that type
# https://github.com/jenkinsci/kubernetes-plugin/tree/master/src/main/java/org/csanchez/jenkins/plugins/kubernetes/volumes
volumes:
# - type: Secret
# secretName: mysecret
# mountPath: /var/myapp/mysecret
NodeSelector: {}
# Key Value selectors. Ex:
# jenkins-agent: v1
Persistence:
Enabled: true
## A manually managed Persistent Volume and Claim
## Requires Persistence.Enabled: true
## If defined, PVC must be created manually before volume will be bound
# ExistingClaim:
## jenkins data Persistent Volume Storage Class
## If defined, storageClassName: <storageClass>
## If set to "-", storageClassName: "", which disables dynamic provisioning
## If undefined (the default) or set to null, no storageClassName spec is
## set, choosing the default provisioner. (gp2 on AWS, standard on
## GKE, AWS & OpenStack)
##
# StorageClass: "-"
Annotations: {}
AccessMode: ReadWriteOnce
Size: 8Gi
volumes:
# - name: nothing
# emptyDir: {}
mounts:
# - mountPath: /var/nothing
# name: nothing
# readOnly: true
NetworkPolicy:
# Enable creation of NetworkPolicy resources.
Enabled: false
# For Kubernetes v1.4, v1.5 and v1.6, use 'extensions/v1beta1'
# For Kubernetes v1.7, use 'networking.k8s.io/v1'
ApiVersion: extensions/v1beta1
## Install Default RBAC roles and bindings
rbac:
install: false
serviceAccountName: default
# RBAC api version (currently either v1beta1 or v1alpha1)
apiVersion: v1beta1
# Cluster role reference
roleRef: cluster-admin
実行イメージとしては以下の通りです。
$ helm --namespace jenkins --name jenkins -f ./jenkins-values.yaml install stable/jenkins --debug
LAST DEPLOYED: Tue Apr 24 12:47:12 2018
NAMESPACE: jenkins
STATUS: DEPLOYED
RESOURCES:
==> v1/Secret
NAME TYPE DATA AGE
jenkins Opaque 2 8m
==> v1/ConfigMap
NAME DATA AGE
jenkins 3 8m
jenkins-tests 1 8m
==> v1/PersistentVolumeClaim
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
jenkins Bound jenkins-jenkins-2c478 8Gi RWO ontap-gold 8m
==> v1/ServiceAccount
NAME SECRETS AGE
jenkins 1 8m
==> v1/Service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
jenkins-agent ClusterIP 10.98.21.68 <none> 50000/TCP 8m
jenkins NodePort 10.96.24.25 <none> 8080:31050/TCP 8m
==> v1beta1/Deployment
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
jenkins 1 1 1 1 8m
==> v1beta1/Ingress
NAME HOSTS ADDRESS PORTS AGE
jenkins jenkins.user21.web.service.consul 80 8m
==> v1/Pod(related)
NAME READY STATUS RESTARTS AGE
jenkins-578686f98d-6pbx9 1/1 Running 0 8m
==> v1beta1/ClusterRoleBinding
NAME AGE
jenkins-role-binding 8m
NOTES:
1. Get your 'admin' user password by running:
printf $(kubectl get secret --namespace jenkins jenkins -o jsonpath="{.data.jenkins-admin-password}" | base64 --decode);echo
2. Visit http://jenkins.user21.web.service.consul
3. Login with the password from step 1 and the username: admin
For more information on running Jenkins on Kubernetes, visit:
https://cloud.google.com/solutions/jenkins-on-container-engine
Configure the Kubernetes plugin in Jenkins to use the following Service Account name jenkins using the following steps:
Create a Jenkins credential of type Kubernetes service account with service account name jenkins
Under configure Jenkins -- Update the credentials config in the cloud section to use the service account credential you created in the step above.
「NOTES」欄に記載の通りadminパスワードを取得します。
$ printf $(kubectl get secret --namespace jenkins jenkins -o jsonpath="{.data.jenkins-admin-password}" | base64 --decode);echo
sShJg2gig9
以上で、Jenkinsのデプロイが完了しました。
Helmが生成するマニフェストファイル¶
Helmを使いvalues.yamlを定義するとどのようなマニフェストファイルが生成されるかが予測しづらいこともあります。
その場合には --dry-run
と --debug
を付与することでデプロイメントされるYAMLファイルが出力されます。
helm –namespace jenkins –name jenkins -f ./values.yaml install stable/jenkins –dry-run –debug
インストールが上手くいかない場合は?¶
values.yamlを試行錯誤しながら設定していくことになると思います。 一度デプロイメントしたHelmチャートは以下のコマンドで削除することができます。
$ helm del --purge チャート名
Helm以外でJenkinsをデプロイした場合¶
本セクションに記載してあることはオプションです。
必要に応じて実施してください。
外部にアプリケーションを公開する方法として Ingress
があります。
Helmを使ってJenkinsをインストールした場合は自動でIngressが作成されます。
それ以外の手法を取った場合は、kubernetesクラスタ外のネットワークからアクセスできるようにIngressを作成してみましょう。
Helm chart を使ってインストールした場合は自動でIngressが導入されています。
そのため、以下の手順はHelmで実施した人は不要です。
Ingressの導入についてはこちらに Ingressを導入する まとめました。
ServiceをDNSへ登録する¶
HelmでデプロイしたJenkinsにはIngress経由でアクセスします。 そのためホスト名を使用してアクセスします。
注釈
なぜそのような仕組みになっているかを知りたい方はJenkinsのHelmチャートをご確認ください。 https://github.com/kubernetes/charts/tree/master/stable/jenkins
今回は名前解決にConsulを使います。
登録用JSONは以下の通りです、TagsとNameでdnsに問い合わせる名前が決まります。
今回はドメインを service.consul
を使用します。
このラボでは命名規則を定義します。
- ID, Tags: アプリケーション識別子.環境番号
- Name: web固定
- Address: 各環境のマスタのIP
アプリケーションにアクセスする際に jenkins.user10.web.service.consul
というFQDNでアクセスしたい場合は以下のjsonファイルを作成します。
ファイル名はwebservice.jsonとします。ポート番号はアプリケーションで使用しているものに変更してください。
{
"ID": "jenkins.user10",
"Name": "web",
"Tags": [ "jenkins.user10" ],
"Address": "192.168.XX.10",
"Port": 80
}
ファイルを作成したら以下のコマンドで登録します。
$ curl -i -s --request PUT --data @webservice.json http://infra1:8500/v1/agent/service/register
HTTP/1.1 200 OK
Date: Wed, 11 Apr 2018 05:31:37 GMT
Content-Length: 0
Content-Type: text/plain; charset=utf-8
登録が完了したら名前解決ができるか確認します。
$ nslookup jenkins.user10.web.service.consul
Jenkinsの設定をする¶
Gitリポジトリに変更があったら自動でテストを実行するジョブを定義します。 このテストは任意で作成してください。
ここでやりたいことは該当リポジトリにコミットがあり、リリースタグが付与された場合に自動でビルド・デプロイをする流れを作成することです。 そのためにはまずJenkinsでGitリポジトリに操作があった場合の動作を定義します。
定義出来る動作としては以下の単位が考えられます。 細かく設定することも可能です。運用に合わせた単位で設定します。
- pull request 単位
- release tag 単位
- 定期実行
前述した以下の項目を盛り込みCI/CDパイプラインを作成しましょう。 以下のようなタスクを組み込んだパイプラインを作成します。シンプルなパイプラインからはじめ、必要に応じてステージを追加していきましょう。
- テスト実行
- アプリケーションビルド
- コンテナイメージのビルド
- レジストリへコンテナイメージのpush
- アプリケーションデプロイ
上記のようなパイプラインを作成にはJenkins pipeline機能が活用できます。
アプリケーションの変更を検知してデプロイメント可能にする¶
CI/CDのパイプラインを作成したら実際にアプリケーションの変更をトリガーに(ソースコードの変更、Gitリポジトリへのpush等)k8sへアプリケーションをデプロイします。
ポリシーとして大きく2つに別れます、参考までに以下に記載いたします。
- デプロイ可能な状態までにし、最後のデプロイメントは人が実施する(クリックするだけ)
- デプロイメントまでを完全自動化する
実際にkubernetes環境へのデプロイができたかの確認とアプリケーションが稼働しているかを確認します。
Helm ChartでCI/CD¶
個別のアプリケーションデプロイメントからHelm Chartを使ったデプロイメントに変更します。
作成したコンテナをHelm Chartを使ってデプロイするようにします。
Helm Chartの開発ガイドは以下のURLを確認ください。
デプロイメントのさらなる進化¶
CI/CDプロセスを成熟させていくと常にリリース可能な状態となっていきます。 そのような状態になると本番環境へのデプロイを迅速にし、ダウンタイムを最小化するための方法が必要になってきます。 元々存在するプラクティスや考え方となりますがコンテナ技術、kubernetesのスケジューラー機能を使うことで今までの環境とくらべて実現がしやすくなっています。
Blue/Greenデプロイメント, Canary リリースというキーワードで紹介したいと思います。
Blue/Greenデプロイメント¶
従来のやり方では1つの環境にデプロイし何かあれば戻すという方法をほとんどのケースで採用していたかと思いますが、さらなる進化として常に戻せる環境を準備し迅速にロールバック 新バージョン、旧バージョンをデプロイしたままルータで切り替えるようになります。
様々な企業で行き着いている運用でもあるかと思いますが、2010年にBlueGreenデプロイメントという名称で説明しています。
実現方法、切り替えのタイミングなどあり、BlueGreenの実装の決定的なものはなく、1つのプラクティスとして存在しています。
2つの環境を準備し、どこかのタイミングで切り替えを行うためDBのマイグレーションの方法などを検討する必要はでてきます。
Canary¶
Canary リリースは BlueGreen デプロイメントと類似したデプロイメントになります。 Blue/Green デプロイメントはすぐに古いバージョンにもどせるように仕組みを整えたものですが、Canaryリリースは新しいバージョン、旧バージョンにアクセスする比率を決めてデプロイするプラクティスです。
こちらは2つの環境ではなく、1環境に複数バージョンのアプリケーションが存在することになります。そのためDBのデータをどのように取り扱うかは検討が必要となります。
まとめ¶
このラボではコンテナ化したアプリケーションのCI/CDパイプラインの構築に挑戦しました。 CI/CDパイプラインを作成するためのJenkins/GitLabをインストールするために必要なHelmの使い方、アプリケーションを外部に公開するためのkubernetesオブジェクトのIngressも併せて使えるようになりました。
ここまでで Level3 は終了です。