5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

GitOpsでBlue/Greenデプロイしてみる

Last updated at Posted at 2020-08-20

はじめに

夏休みの自由研究的なノリで、GitOps的な方法でBlue/Green Deploymentを自動化できるかな、とふと思い立ってやってみました
最終的に完全な自動化はできてはいないものの、現時点でできているものと所感をまとめていきます

まだまだDevOpsもKubernetesもちょっとかじった程度なので、ベストプラクティスとは程遠いのであしからず
あと、文章書くのヘタクソなので説明が不十分だったりしますが、どうか許してください

使ったツール

  • GitHub
  • CircleCI
  • Kustomize
  • ArgoCD
  • Kubernetes @ EKS
  • Istio

ざっくり概要

  1. GitHubにアプリケーションリポジトリとK8s Manifestリポジトリをそれぞれ作って、アプリケーションのソースコードとDockerfileとビルド用のCircleCIのconfig.ymlを作成してアプリケーションリポジトリにPush
  2. CircleCIでDockerfileビルドしてECRにPushして、新しいイメージのデプロイ用にDeploymentとVirtualServiceそれぞれのkustomization.yamlを更新して、K8s ManifestリポジトリにPush & Pull Request
  3. GitHubでPRマージ(これは手動じゃないとダメだね)
  4. ArgoCDでK8s Manifestを同期して新しいイメージをBlue or Greenにのみデプロイ
  5. デプロイしたアプリケーションが正しく動いているのを確認できたらBlue/Green切り替えのVirtualServiceもArgoCDで同期(ここ手動だけど自動化できるといいな)

要件(というかこだわりたかったこと)

  • ベースのマニフェストはそのままで、デプロイにかかる変更は全てkustomization.yamlで完結させたい
  • トレースやログで監視できるように新しいアプリケーションのバージョンをPodのラベルで管理したい
  • Deploymentを更新した後はすぐに自動的にトラフィック切り替えるのではなく、テストした後に切り替えるようにしたい

設定つくってみる

Kubernetesマニフェスト

ファイル・ディレクトリ構成がArgoCDに設定に大きく関わってくるので、試行錯誤しながら最終的に以下のようにしました

gorilla-sfx-demo
└─ eks
    ├─ deployment
    │   ├─ blue
    │   │   ├─ gorilla-sfx-demo-deployment-blue.yaml
    │   │   └─ kustomization.yaml
    │   └─ green
    │       ├─ gorilla-sfx-demo-deployment-green.yaml
    │       └─ kustomization.yaml
    ├─ virtualservice
    │   ├─ gorilla-sfx-demo-istio-virtualservice-blue.yaml
    │   ├─ gorilla-sfx-demo-istio-virtualservice-green.yaml
    │   └─ kustomization.yaml
    ├─ gorilla-sfx-demo-istio-destination.yaml
    ├─ gorilla-sfx-demo-service.yaml
    └─ kustomization.yaml

ちなみにファイル名やディレクトリ名の gorilla というのはGorilla/Muxで作ったデモ用アプリケーションなので、ゴリラアプリとかそういうものではないです

DeploymentはBlueとGreenそれぞれ別々に作って置いてあります

kubectl get pods -l app=gorilla-sfx-demo
NAME                                                 READY   STATUS    RESTARTS   AGE
gorilla-sfx-demo-deployment-blue-f9784849d-jz8f6     2/2     Running   2          2d
gorilla-sfx-demo-deployment-blue-f9784849d-pmpk5     2/2     Running   0          2d
gorilla-sfx-demo-deployment-green-7db5b8b754-jrdzz   2/2     Running   0          2d
gorilla-sfx-demo-deployment-green-7db5b8b754-ltz8c   2/2     Running   0          2d

それぞれの kustomization.yaml は↓

eks/deployment/blue/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
images:
- name: ************.dkr.ecr.**-****-*.amazonaws.com/gorilla-sfx-demo
  newTag: latest
commonAnnotations:
  version: latest
commonLabels:
  version: latest
resources:
- gorilla-sfx-demo-deployment-blue.yaml
eks/deployment/green/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
images:
- name: ************.dkr.ecr.**-****-*.amazonaws.com/gorilla-sfx-demo
  newTag: latest
commonAnnotations:
  version: latest
commonLabels:
  version: latest
resources:
- gorilla-sfx-demo-deployment-green.yaml
eks/virtualservice/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- gorilla-sfx-demo-istio-virtualservice-blue.yaml

VirtualServiceは↓のようにBlue/Greenそれぞれ作成

eks/virtualservice/gorilla-sfx-demo-istio-virtualservice-blue.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: gorilla-sfx-demo-virtualservice
  namespace: default
spec:
  hosts:
  - gorilla-sfx-demo-service
  http:
  - match:
    - headers:
        x-deploy:
          exact: green
    route:
    - destination:
        host: gorilla-sfx-demo-service
        subset: green
        port:
          number: 9090
  - match:
    - uri:
        prefix: /
    route:
    - destination:
        host: gorilla-sfx-demo-service
        subset: blue
        port:
          number: 9090
eks/virtualservice/gorilla-sfx-demo-istio-virtualservice-green.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: gorilla-sfx-demo-virtualservice
  namespace: default
spec:
  hosts:
  - gorilla-sfx-demo-service
  http:
  - match:
    - headers:
        x-deploy:
          exact: blue
    route:
    - destination:
        host: gorilla-sfx-demo-service
        subset: blue
        port:
          number: 9090
  - match:
    - uri:
        prefix: /
    route:
    - destination:
        host: gorilla-sfx-demo-service
        subset: green
        port:
          number: 9090

本番トラフィックが流れてない方には x-deploy ヘッダを使ってリクエストをルートしてテストできるようにしてあります
で、そのヘッダには必ず bluegreen かで指定するようにしてあります

この記事 を参考にしました とても勉強になりました

ArgoCD

こんな感じ↓
argocd_blue_green_deploy.png
gorilla-sfx-demo: DeploymentとVirtualService以外の同期用(ディレクトリrecurseはしない)
gorilla-sfx-demo-deployment-blue: BlueのDeployment同期用
gorilla-sfx-demo-deployment-green: GreenのDeployment同期用
gorilla-sfx-demo-virtualservice: VirtualServiceでトラフィック切り替え用

マニフェストを全て git push してmasterにマージしたら、ArgoCDでこれら全てをSyncして展開しよう

CircleCI設定

.circleci/config.yml を以下のように作ってアプリケーションリポジトリにPush

.circleci/config.yml
version: 2.1
jobs:
  manifest_pull_request:
    docker:
      # specify the version you desire here
      - image: cimg/base:stable-18.04

    working_directory: ~/

    environment:
      GH_CLI_VERSION: "0.11.1"

    parameters:
      env:
        default: eks
        description: Kubernetes environment (eks, minikube, etc)
        type: string

    steps:
      - kube/install-kubectl
      - kube/install-kubeconfig:
          kubeconfig: KUBECONFIG_DATA

      - aws-cli/install
      - aws-cli/setup:
          aws-access-key-id: AWS_ACCESS_KEY_ID
          aws-secret-access-key: AWS_SECRET_ACCESS_KEY
          aws-region: AWS_REGION

      - run:
          name: install gh
          command: |
            wget https://github.com/cli/cli/releases/download/v${GH_CLI_VERSION}/gh_${GH_CLI_VERSION}_linux_amd64.deb
            sudo dpkg --install ./gh_${GH_CLI_VERSION}_linux_amd64.deb
      - run:
          name: install kustomize
          command: |
            curl -s "https://raw.githubusercontent.com/\
            kubernetes-sigs/kustomize/master/hack/install_kustomize.sh"  | bash
      - run:
          name: clone manifest repository
          command: |
            git config --global user.name ${GITHUB_USERNAME}
            git config --global user.email ${GITHUB_EMAIL}
            git clone https://${GITHUB_USERNAME}:${GITHUB_TOKEN}@github.com/${GITHUB_ORG}/${GITHUB_REPOSITORY}.git
      - run:
          name: get next deployment zone (blue/green)
          command: |
            kubectl get vs gorilla-sfx-demo-virtualservice -o yaml
            echo "export NEXT_DEPLOY=`kubectl get vs gorilla-sfx-demo-virtualservice -o jsonpath='{.spec.http[].match[].headers.x-deploy.exact}'`" >> $BASH_ENV
      - run:
          name: update deployment manifest
          command: |
            cd ~/${GITHUB_REPOSITORY}/${APP_NAME}/<<parameters.env>>/deployment/${NEXT_DEPLOY}
            git checkout -b gitops-${GITHUB_USERNAME}-${CIRCLE_SHA1}
            # Set new image tag
            ~/kustomize edit set image ${AWS_ECR_ACCOUNT_URL}/${AWS_ECR_REPOSITORY}:${CIRCLE_SHA1}
            # Set version as annotation and label
            ~/kustomize edit remove annotation version || true
            ~/kustomize edit add annotation version:${CIRCLE_SHA1}
            ~/kustomize edit remove label version || true
            ~/kustomize edit add label version:`echo ${CIRCLE_SHA1} | cut -b 1-7`
      - run:
          name: update virtualservice manifest
          command: |
            cd ~/${GITHUB_REPOSITORY}/${APP_NAME}/<<parameters.env>>/virtualservice
            # Set virtual service resource for blue/green deployment
            ~/kustomize edit remove resource *
            ~/kustomize edit add resource ${APP_NAME}-istio-virtualservice-${NEXT_DEPLOY}.yaml
      - run:
          name: git commit and push
          command: |
            cd ~/${GITHUB_REPOSITORY}
            git add .
            git commit -m "update ${APP_NAME} manifest for ${CIRCLE_SHA1}"
            git push origin gitops-${GITHUB_USERNAME}-${CIRCLE_SHA1}
      - run:
          name: create pull request
          command: |
            cd ~/${GITHUB_REPOSITORY}
            gh pr create \
              -t "deploy a new image ${APP_NAME}:${CIRCLE_SHA1}" \
              -b "CircleCI auto-generated pull request from https://github.com/${GITHUB_ORG}/${APP_NAME}/commit/${CIRCLE_SHA1}"

orbs:
  aws-ecr: circleci/aws-ecr@6.12.2
  kube: circleci/kubernetes@0.11.1
  aws-cli: circleci/aws-cli@1.2.1

workflows:
  main:
    jobs:
      - aws-ecr/build-and-push-image:
          account-url: AWS_ECR_ACCOUNT_URL
          aws-access-key-id: AWS_ACCESS_KEY_ID
          aws-secret-access-key: AWS_SECRET_ACCESS_KEY
          checkout: true
          create-repo: false
          dockerfile: Dockerfile
          region: AWS_REGION
          repo: ${AWS_ECR_REPOSITORY}
          skip-when-tags-exist: false
          tag: 'latest,${CIRCLE_SHA1}'
          context: my-context
          filters:
            branches:
              only: 
                - master
      - manifest_pull_request:
          env: eks
          context: my-context
          requires:
            - aws-ecr/build-and-push-image

設定のポイント

get next deployment zone (blue/green) ステップ

      - run:
          name: get next deployment zone (blue/green)
          command: |
            kubectl get vs gorilla-sfx-demo-virtualservice -o yaml
            echo "export NEXT_DEPLOY=`kubectl get vs gorilla-sfx-demo-virtualservice -o jsonpath='{.spec.http[].match[].headers.x-deploy.exact}'`" >> $BASH_ENV

Blue/Greenデプロイの場合、今BlueかGreenどちらが動いているかを判別して、反対側を更新する必要があるので、VirtualServiceの x-deploy ヘッダがどちらに向いてるかを調べます
x-deployが green のときは、本番トラフィックは blue に向かってるので、次にデプロイするべきは green ということになりますね

で、それを調べるためには kubectl が必要で、更にEKSの場合kubeconfigに aws CLIを使っているので、 以下のステップが必要になるわけです

      - kube/install-kubectl
      - kube/install-kubeconfig:
          kubeconfig: KUBECONFIG_DATA

      - aws-cli/install
      - aws-cli/setup:
          aws-access-key-id: AWS_ACCESS_KEY_ID
          aws-secret-access-key: AWS_SECRET_ACCESS_KEY
          aws-region: AWS_REGION

update deployment manifest ステップ / update virtualservice manifest ステップ

      - run:
          name: update deployment manifest
          command: |
            cd ~/${GITHUB_REPOSITORY}/${APP_NAME}/<<parameters.env>>/deployment/${NEXT_DEPLOY}
            git checkout -b gitops-${GITHUB_USERNAME}-${CIRCLE_SHA1}
            # Set new image tag
            ~/kustomize edit set image ${AWS_ECR_ACCOUNT_URL}/${AWS_ECR_REPOSITORY}:${CIRCLE_SHA1}
            # Set version as annotation and label
            ~/kustomize edit remove annotation version || true
            ~/kustomize edit add annotation version:${CIRCLE_SHA1}
            ~/kustomize edit remove label version || true
            ~/kustomize edit add label version:`echo ${CIRCLE_SHA1} | cut -b 1-7`
      - run:
          name: update virtualservice manifest
          command: |
            cd ~/${GITHUB_REPOSITORY}/${APP_NAME}/<<parameters.env>>/virtualservice
            # Set virtual service resource for blue/green deployment
            ~/kustomize edit remove resource *
            ~/kustomize edit add resource ${APP_NAME}-istio-virtualservice-${NEXT_DEPLOY}.yaml

kustomize CLI使って、Deploymentではラベルとかイメージのタグをセットしてます(Annotationはオマケ程度です)

VirtualServiceは resources をblueかgreenで切り替えるようにしてます
(わざわざblue用とgree用それぞれのVirtualServiceマニフェスト作っといたのはこのためです)

create pull request ステップ

      - run:
          name: create pull request
          command: |
            cd ~/${GITHUB_REPOSITORY}
            gh pr create \
              -t "deploy a new image ${APP_NAME}:${CIRCLE_SHA1}" \
              -b "CircleCI auto-generated pull request from https://github.com/${GITHUB_ORG}/${APP_NAME}/commit/${CIRCLE_SHA1}"

ここではPR作ってるだけですが、 gh CLI を使ってみました
この記事書いてる時点ではまだベータですけど、CLIで簡単にPR出せるし他のこともいろいろできそうなので試してみたくなって、ついつい、ね・・・

出来上がってくるファイル

Deploymentのkustomization.yamlでは、本番トラフィックが来てない方(今回はgreen)のDeploymentでイメージタグやラベルが更新されます

eks/deployment/green/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
images:
- name: ************.dkr.ecr.**-****-*.amazonaws.com/gorilla-sfx-demo
  newTag: <GIT_COMMIT_SHA>
commonAnnotations:
  version: <GIT_COMMIT_SHA>
commonLabels:
  version: <GIT_COMMIT_SHA_SHORT>
resources:
- gorilla-sfx-demo-deployment-green.yaml

VirtualServiceは、次のデプロイでトラフィックが切り替わる方のVirtualServiceが指定されています

eks/virtualservice/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- gorilla-sfx-demo-istio-virtualservice-green.yaml

PRマージしてArgoCD同期

ここで一つどうしようもない問題が出てきます
kustomizeで commonLabels に設定すると厄介なことにselectorのラベルにも適用されてしまうんです
Kubernetesではselectorラベルはimmutable(不変)であるため、 versioncommonLabels に指定すると、Deploymentを更新できなくなってしまいます

こんなふうに↓怒られます

The Deployment "gorilla-sfx-demo-deployment-green" is invalid: spec.selector: Invalid value: v1.LabelSelector{MatchLabels:map[string]string{"app":"gorilla-sfx-demo", "deploy":"green", "version":"xxxxxxx"}, MatchExpressions:[]v1.LabelSelectorRequirement(nil)}: field is immutable

なので、回避策としてDeploymentは手動で強制同期します

argocd app sync gorilla-sfx-demo-deployment-green --force

これ、なんとかならないですかね?良い方法あればコメントいただけるとありがたいです
なるべくkustomize CLIとkustomization.yamlで完結するようにしたいんだけどなあ

ちなみにこんな↓issue見つけたのですが、未だ解決に至ってないようです
https://github.com/kubernetes-sigs/kustomize/issues/1009

テストしてVirtualService切り替え

適当なPodで wget でテスト

kubectl run -it --rm --image=busybox:latest --restart=Never \
--overrides='{ "apiVersion": "v1", "metadata": {"annotations": { "sidecar.istio.io/inject":"false" } } }'  \
busybox -- wget -qO- gorilla-sfx-demo-service:9090/healthz

テスト結果がOKだったらVirtualServiceも同期して切り替え

argocd app sync gorilla-sfx-demo-virtualservice

これで新しくデプロイしたgreenにトラフィックが向かいます

できればこのテストと同期も自動化したいな
良い方法ないかな?

やってみた感想

ここまでやっといて何ですけど、GitOpsとBlue/Greenデプロイは相性が悪そうですね

  • デプロイ設定そのものがVirtualServiceで設定した x-deploy ヘッダでの制御に頼らざるを得ない
  • マニフェストのディレクトリ構成とかArgoCDの設定が複雑になりがち
  • CircleCIでインストールしなきゃいけないものが大杉
  • kustomizeの commonLabels がイケてない(じゃあ使うなよって話だけど)

ということで、やっぱりSpinnakerの方がいいのかなあ
まあ今回のやり方はあまり実運用向きじゃなさそうということがわかっただけでも収穫ありということにしておくか

5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?