絶賛k8sのデプロイをCircleCI化してる…!
— adachin👾SRE (@adachin0817) February 18, 2023
[DigitalOcean][Kubernetes][Container Registry]独自のWordPressコンテナイメージに移行してみた
半年前に個人のDigitalOceanで運用しているKubernetes(wiki.adachin.me)をWordPress公式コンテナイメージから独自イメージに移行しました。そこでようやく、CircleCIで自動デプロイとローリングアップデートを実装してみたのでブログします。
デプロイ構成
CircleCIにはOrbsがありますが、DigitalOceanには対応していません。そのため職人のようにシェルを自前で実装する必要があります。ですが、Kubernetesが提供しているkubectlがうまいことやってくれますので、そこまでハマることはないと思います。(自分は5時間かかりましたが…)簡単にデプロイの流れとしては以下となります。
- CircleCIのpath-filteringで特定ディレクトリに差分があれば実行
- docker/prd/wiki/.*
- k8s/wiki/manifests/wordpress.yaml
- kubectl、doctlパッケージをインストール
- docker build、tag、Containter Registryにpush
- kubectl applyでローリングアップデート
準備
まずはローカルで利用するDigitalOceanが提供しているコマンドライン・ツール doctl(awscli的な)
と kubectl
が利用できるように初期設定をしましょう。これらをCircleCIで駆使します。
Fix CircleCI
- リポジトリ構成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
> tree docker docker ├── dev │ └── terraform │ ├── Dockerfile │ ├── README.md │ ├── docker-compose.yml │ └── homedir └── prd └── wiki ├── Dockerfile ├── README.md ├── nginx │ ├── nginx.conf │ └── wiki.adachin.me.conf ├── php │ ├── php-fpm.conf │ ├── php.ini │ └── www.conf └── supervisor ├── app.conf └── supervisord.conf > tree k8s k8s └── wiki ├── README.md ├── deploy │ └── deploy-wiki.sh └── manifests ├── lb.yaml ├── mackerel.yaml ├── wordpress-latest.yaml ├── wordpress-volume.yaml └── wordpress.yaml |
- CircleCI側で設定するEnvironment Variables(環境変数)
- KUBERNETES_CLUSTER_CERTIFICATE(/.kube/configのcertificate-authority-dataを指定)
- DO_API_TOKEN(doctlで利用するAPIを指定)
- .circleci/config.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
version: 2.1 setup: true orbs: path-filtering: circleci/path-filtering@0.1.3 workflows: version: 2.1 terraform_ci_cd_deploy_wiki: jobs: - path-filtering/filter: base-revision: origin/master config-path: .circleci/workflows.yml mapping: | terraform/prd/.* prd_terraform_ci true terraform/prd/.* prd_terraform_apply true docker/prd/wiki/.* deploy_prd_wiki true #追加 k8s/wiki/manifests/wordpress.yaml deploy_prd_wiki true #追加 |
path-filtering
を利用しているので、docker/prd/wiki、k8s/wiki/manifests/wordpress.yamlに差分があった場合にデプロイするように追加しました。
- .circleci/workflows.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
version: 2.1 orbs: git-shallow-clone: guitarrapc/git-shallow-clone@2.4.0 terraform: circleci/terraform@3.1.0 slack: circleci/slack@4.10.1 parameters: ~省略~ deploy_prd_wiki: #差分検知するためにパラメーターを追加 type: boolean default: false references: wiki_config: &wiki_config #wikiコンテナをデプロイするコンテナイメージの追加 docker: - image: cimg/base:edge resource_class: small working_directory: ~/wiki wiki_base_install: &wiki_base_install #事前に利用するパッケージをインストール run: name: wiki_base_install kubectl,doctl command: | sudo apt-get -y update && sudo apt-get -y install gettext-base curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl chmod u+x ./kubectl sudo mv ./kubectl /usr/local/bin wget https://github.com/digitalocean/doctl/releases/download/v1.92.1/doctl-1.92.1-linux-amd64.tar.gz tar xf ./doctl-1.92.1-linux-amd64.tar.gz sudo mv ./doctl /usr/local/bin |
docker buildで利用するbaseイメージは cimg/base:edge
を選択しました。また、事前にインストールする kubectl
と doctl
は references
で管理しました。(ここはシェル化しちゃっても良さそう)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
jobs: deploy_prd_wiki: environment: IMAGE_NAME: registry.digitalocean.com/wiki-wordpress/wiki-wordpress <<: *wiki_config steps: - checkout - setup_remote_docker - *wiki_base_install - run: name: Build Docker image command: | docker build -f docker/prd/wiki/Dockerfile -t wiki-wordpress:latest . docker tag wiki-wordpress:latest $IMAGE_NAME:$CIRCLE_SHA1 #docker tagはコミットハッシュ化 - run: name: Push Docker Image command: | doctl auth init -t "${DO_API_TOKEN}" doctl kubernetes cluster kubeconfig save adachin-wiki-cluster doctl registry login docker push $IMAGE_NAME:$CIRCLE_SHA1 #コミットハッシュでdocker push - run: name: Deploy wiki command: ./k8s/wiki/deploy/deploy-wiki.sh #デプロイシェルでローリングアップデート - slack-notify-fail - slack-notify-success workflows: version: 2.1 check_no_difference: jobs: - check_no_difference when: and: - not: << pipeline.parameters.deploy_prd_wiki >> ~省略~ deploy_prd_wiki: when: << pipeline.parameters.deploy_prd_wiki >> jobs: - deploy_prd_wiki: filters: branches: only: master #masterマージで発動 |
docker buildとtagの14行目は $CIRCLECI_SHA1
で最終コミットを識別するハッシュ値を指定しました(Orbsの裏側も同じなはず)。ちなみにdocker imageのtagをlatestで管理してしまうと何かあったときに元に戻せなくなってしまうため、コミットハッシュがベストです。この変数を利用して後ほど出てくるデプロイシェルや、manifestsファイルでも利用します。
- deploy-wiki.sh
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#! /bin/bash set -e COMMIT_SHA1=$CIRCLE_SHA1 export COMMIT_SHA1=$COMMIT_SHA1 envsubst <~/wiki/k8s/wiki/manifests/wordpress.yaml >~/wiki/k8s/wiki/manifests/wordpress.yaml.out mv ~/wiki/k8s/wiki/manifests/wordpress.yaml.out ~/wiki/k8s/wiki/manifests/wordpress.yaml echo "$KUBERNETES_CLUSTER_CERTIFICATE" | base64 --decode > cert.crt kubectl --certificate-authority=cert.crt apply -f ~/wiki/k8s/wiki/manifests/wordpress.yaml yes | doctl registry garbage-collection start wiki-wordpress |
このデプロイスクリプトで工夫しなければならないことは上記で指定した CIRCLE_SHA1
コミットハッシュをmanifestsファイル内でimage URLとして環境変数に渡さなければなりません。そこでsedを使う方法もありますが、可読性は低くなってしまうので、enbsubstコマンドを利用しました。変数展開などテンプレートエンジンのような仕組みを作れるので非常に便利です。最後の段落ではContainer Registry(garbage-collection)の容量が貯まるため、毎回最適化しています。最後にmanifestsファイルを見てみましょう。
Fix Manifest
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
--- apiVersion: apps/v1 kind: StatefulSet metadata: name: wiki-wordpress spec: serviceName: "wiki-wordpress" replicas: 2 #Podsは2つに変更 updateStrategy: #ローリングアップデートをするために追加 type: RollingUpdate rollingUpdate: selector: matchLabels: app: wiki-wordpress template: metadata: labels: app: wiki-wordpress spec: containers: - name: wiki-wordpress image: registry.digitalocean.com/wiki-wordpress/wiki-wordpress:$COMMIT_SHA1 #コミットハッシュの変数を指定 ports: - containerPort: 80 name: wiki-wordpress volumeMounts: - name: wordpress-data mountPath: /var/www/wiki.adachin.me volumes: - name: wordpress-data persistentVolumeClaim: claimName: wordpress-volume volumeClaimTemplates: - metadata: name: wordpress-volume spec: accessModes: [ "ReadWriteOnce" ] resources: requests: storage: 10Gi storageClassName: do-block-storage |
manifestsファイルでは8行目でreplicasを2台に変更と、updateStrategyではRollingUpdateを追加しました。22行目では先程のtagをコミットハッシュとして指定しています。
ところで、replicasを1台から2台に変更した理由としてはデプロイ時のローリングアップデートで1台だと何故かLBから外れて503になってしまいました。ヘルスチェックの調整が必要そうなので、後ほど調査します。一旦デプロイ時のダウンタイムは防ぐことができました。また、クラスターは1core メモリー2GBなので、php-fpmのチューニングをしました。
1 2 3 4 5 6 7 8 |
pm = static pm.max_children = 10 pm.start_servers = 10 pm.min_spare_servers = 10 pm.max_spare_servers = 10 pm.process_idle_timeout = 10s; pm.max_requests = 100 request_terminate_timeout = 100 |
ちなみにkindで StatefulSet/ステートフル/状態を持つPod
を指定している理由は volumeClaimTemplates
でWordPressのソースコードをマウントしているためです。今後はリポジトリでソースコードも管理しようと思いますので、 Deployment/ステートレス/状態を持たないPod
に変更したほうが良さそうです。以下参考にさせていただきました。
https://sorarinu.dev/2021/08/kubernetes_01/
※Deploymentに移行してローリングアップデートのダウンタイムを改善しました
[Kubernetes]ローリングアップデート時のダウンタイムを改善してStatefulSetからDeploymentに移行した
Test Deploy
- Rolling Update
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
$ kubectl get pods NAME READY STATUS RESTARTS AGE mackerel-agent 1/1 Running 0 179d wiki-wordpress-0 1/1 Running 0 17m wiki-wordpress-1 1/1 Running 0 17m $ kubectl get pods NAME READY STATUS RESTARTS AGE mackerel-agent 1/1 Running 0 179d wiki-wordpress-0 1/1 Running 0 18m wiki-wordpress-1 0/1 ContainerCreating 0 8s $ kubectl get pods NAME READY STATUS RESTARTS AGE mackerel-agent 1/1 Running 0 179d wiki-wordpress-0 1/1 Terminating 0 18m wiki-wordpress-1 1/1 Running 0 25s $ kubectl get pods NAME READY STATUS RESTARTS AGE mackerel-agent 1/1 Running 0 179d wiki-wordpress-0 0/1 ContainerCreating 0 1s wiki-wordpress-1 1/1 Running 0 28s $ kubectl get pods NAME READY STATUS RESTARTS AGE mackerel-agent 1/1 Running 0 179d wiki-wordpress-0 1/1 Running 0 16s wiki-wordpress-1 1/1 Running 0 43s |
- Podsのimage確認
1 2 3 |
$ kubectl get pods --all-namespaces -o jsonpath="{.items[*].spec.containers[*].image}" registry.digitalocean.com/wiki-wordpress/wiki-wordpress:7xxxxxxxxxxxxxxx registry.digitalocean.com/wiki-wordpress/wiki-wordpress:7xxxxxxxxxxxxxxx |
まとめ
個人のk8sちゃんをCircleCIで自動デプロイできた。懸念点としてはローリングアップデート時にPodが1台だと切り替わる時にアクセスができなくなってしまうな…2台だと問題ないけどもここらへんは知識不足である。
— adachin👾SRE (@adachin0817) February 18, 2023
今回Kubernetesでの自動デプロイは初めて実装しました。Kubernetesは独自の知識が必要なので、デバッグには少々時間がかかりましたが、ようやくやりたかったことができたので満足です。プライベート充実!次はソースコードをリポジトリで管理しようと思いますので、また改善できたらブログしたいと思います。それでは!
参考
0件のコメント