TECHSTEP

ITインフラ関連の記事を公開してます。

【メモ】CronJob経由で定期的にKubernetesリソースをデプロイする

はじめに

今回はKubernetesCronJobリソースでkubectlコマンドを実行し、定期的にリソースを更新する方法について、メモを残しておきます。

やりたいことは以下の通りです。

今回はgit kubectlそれぞれのコマンドを実行することのできるコンテナイメージを利用し、2つのコンテナ間でEmptyDir Volumeを共有することで、マニフェストファイルのデプロイを実行しました。

CronJobの利用方法

CronJobからkubectlコマンドを実行するには、CronJobkubectlコマンドを実行できるよう権限を付与する必要があります。そのため、まずはService AccountとRole/Rolebindingを作成します。今回はDeploymentリソースの作成を目的としていたため、以下のようなRoleを付与しました。

apiVersion: v1
kind: ServiceAccount
metadata:
  name: internal-kubectl
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: modify-deployments
rules:
  - apiGroups: ["apps"]
    resources:
      - deployments
    verbs:
      - get
      - create
      - update
      - patch
      - list
      - delete
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: modify-pods-to-sa
subjects:
  - kind: ServiceAccount
    name: internal-kubectl
roleRef:
  kind: Role
  name: modify-deployments
  apiGroup: rbac.authorization.k8s.io

また、利用するGitHubリポジトリがプライベートリポジトリの場合は、以下のようなSecretリソースをあらかじめ作成しておきます。

apiVersion: v1
kind: Secret
metadata:
  name: forgithubaccess
type: Opaque
data:
  username: <base64-GItHub account>
  password: <base64-GitHub access token>

これでCronJobを利用する準備ができたので、次にCronJobを作成します。今回は、以下のような定義ファイルを利用しました。

apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: cronjob-git-kubectl
spec:
  schedule: "*/1 * * * *"  # 毎分実行する
  jobTemplate:
    spec:
      template:
        spec:
          serviceAccountName: internal-kubectl  # 事前に作成したService Accountを指定
          containers:
          - name: git
            image: alpine/git:latest
            env:
              - name: SECRET_USERNAME
                valueFrom:
                  secretKeyRef:
                    name: forgithubaccess
                    key: username
              - name: SECRET_PASSWORD
                valueFrom:
                  secretKeyRef:
                    name: forgithubaccess
                    key: password
            command: ["git", "clone"]
            args: ["https://$(SECRET_USERNAME):$(SECRET_PASSWORD)@github.com/<Account>/<Repository>.git", "/cache"]
            volumeMounts:
            - mountPath: /cache
              name: git-volume
          - name: kubectl
            image: bitnami/kubectl
            command: ["/bin/sh", "-c"]
            args: 
            - |
              sleep 10;  # 上のgit cloneが終了するまで待機
              kubectl apply -f /cache/sample-app.yaml  # GitHubリポジトリのディレクトリ・ファイル構成に応じて変更
            volumeMounts:
            - mountPath: /cache
              name: git-volume
          volumes:
          - name: git-volume
            emptyDir: {}
          restartPolicy: Never  
      backoffLimit: 4

実行内容としては、①対象の定義ファイルを格納したGitリポジトリからファイルを取得する、②取得したファイルを指定してkubectl applyコマンドを実行する、という流れになります。①の処理が完了する前に②を実行すると、「指定したファイルが存在しない」というエラーが発生するため、sleepで一定時間待機してからkubectlコマンドを実行するようにしています。

CronJob中で2つのコンテナを実行し、取得したリポジトリのデータを共有できるようemptyDirリソースを利用しています。

CronJobのスケジューリングはひとまず1分ごとに実行するよう設定し、1分ごとに①②の操作を繰り返すことになります。

これを利用してCronJobを作成します。

$ kubectl apply -f cronjob-git-kubectl.yml
cronjob.batch/cronjob-git-kubectl created


$ kubectl get cronjob
NAME                  SCHEDULE      SUSPEND   ACTIVE   LAST SCHEDULE   AGE
cronjob-git-kubectl   */1 * * * *   False     0        <none>          6s

作成後にPodの状態を確認すると、1分ごとにJobが起動し、動作している様子が確認できます。

# CronJobの様子
# “sample-app”がkubectlコマンドで作成したPod

$ kubectl get pods -w
NAME                                          READY   STATUS    RESTARTS   AGE
cronjob-git-kubectl-1607865300-ff4q6          0/2     Pending   0          0s
cronjob-git-kubectl-1607865300-ff4q6          0/2     Pending   0          0s
cronjob-git-kubectl-1607865300-ff4q6          0/2     ContainerCreating   0          0s

# (1回目のJob実行)

cronjob-git-kubectl-1607865300-ff4q6          1/2     Running             0          13s
sample-app-75ff8897f5-rgmgt                   0/1     Pending             0          0s
sample-app-75ff8897f5-rgmgt                   0/1     Pending             0          0s
sample-app-75ff8897f5-rgmgt                   0/1     ContainerCreating   0          0s
cronjob-git-kubectl-1607865300-ff4q6          0/2     Completed           0          23s
sample-app-75ff8897f5-rgmgt                   1/1     Running             0          9s


# (2回目以降のJob実行)

cronjob-git-kubectl-1607865360-9h7ds          0/2     Pending             0          0s
cronjob-git-kubectl-1607865360-9h7ds          0/2     Pending             0          0s
cronjob-git-kubectl-1607865360-9h7ds          0/2     ContainerCreating   0          0s
cronjob-git-kubectl-1607865360-9h7ds          1/2     Running             0          7s
cronjob-git-kubectl-1607865360-9h7ds          0/2     Completed           0          17s
cronjob-git-kubectl-1607865420-6ncqj          0/2     Pending             0          0s
cronjob-git-kubectl-1607865420-6ncqj          0/2     Pending             0          0s
cronjob-git-kubectl-1607865420-6ncqj          0/2     ContainerCreating   0          1s
cronjob-git-kubectl-1607865420-6ncqj          1/2     Running             0          8s
cronjob-git-kubectl-1607865420-6ncqj          0/2     Completed           0          18s


# 実行結果

$  kubectl get pods
NAME                                          READY   STATUS      RESTARTS   AGE
cronjob-git-kubectl-1607865300-ff4q6          0/2     Completed   0          2m26s
cronjob-git-kubectl-1607865360-9h7ds          0/2     Completed   0          86s
cronjob-git-kubectl-1607865420-6ncqj          0/2     Completed   0          26s
sample-app-75ff8897f5-rgmgt                   1/1     Running     0          2m4s


# 1回目のJobのログ
$ kubectl logs cronjob-git-kubectl-1607865300-ff4q6 -c git
Cloning into '/cache'...

$ kubectl logs cronjob-git-kubectl-1607865300-ff4q6 -c kubectl
deployment.apps/sample-app created


# 2回目以降のJobのログ
$ kubectl logs cronjob-git-kubectl-1607865420-6ncqj -c git
Cloning into '/cache'...

$ kubectl logs cronjob-git-kubectl-1607865420-6ncqj -c kubectl
deployment.apps/sample-app unchanged

参考ドキュメント

External Secretsを試してみる

今回はKubernetes上で扱う秘匿情報を管理するExternal Secretsを試してみました。

注意事項:本記事はExternal SecretsのリポジトリGoDaddyからexternal-secretsに移管する前に検証をしていたものです。そのため、一部マニフェストファイルにはgodaddy/kubernetes-external-secretsと記載されております。

リポジトリ移管の経緯はGitHub Issueに記載されております。

Kubernetesで利用するSecretの課題と関連プロダクト

Kubernetesはログインパスワードなどの秘匿情報をSecretリソースで管理することが一般的な方法です。Secretリソースはbase64で暗号化した値を設定する必要があり、これはSecretリソースにアクセスできてしまえば、簡単に復号化できてしまう情報になります。

apiVersion: v1
kind: Secret
metadata:
  name: mysecret
type: Opaque
data:
  username: YWRtaW4=  # admin
  password: YWRtaW5wYXNzd29yZAo=  # adminpassword
$ echo YWRtaW4= | base64 --decode
admin
$ echo YWRtaW5wYXNzd29yZAo= | base64 --decode
adminpassword

一方でKubernetesマニフェストファイルをGitHubで管理してGitOpsを行う場合、Secretを含むマニフェストファイルをGitHubリポジトリに保管するため、リポジトリにアクセスできるユーザーは誰でも秘匿情報を知ることができてしまいます。

このようなケースに対応するため、KubernetesのSecret情報を管理するプロダクトは複数開発されております。代表的なものとして、以下のものが挙げられます。

  • kubesec: gpg / AWS KMS / Google Cloud KMSなどの鍵情報を利用してSecretリソースを暗号化する。kubesecコマンドを実行して新規・既存のSecretリソースを作成することができる。
  • Sealed Secret: SealedSecret Operatorとkubesealコマンド、デプロイ時にクラスター上に作成される公開鍵・秘密鍵を組み合わせる。kubesealコマンドによってSecretリソースを暗号化したテンプレートが用意され、そのテンプレートを利用してSealedSecretリソースを作成することでSecretリソースが作成される。
  • External Secrets: ExternalSecrets Controllerを利用して、外部のサービスに保管された秘匿情報からSecretリソースを作成する。
  • Berglas: Google Cloud Storage / Google Cloud KMS等を利用して秘匿情報を管理するツール。Kubernetes Secretリソースに対してはMutatingWebhookConfigurationを利用して、Google Cloud Secret Managerに保管した秘匿情報をDeploymentなどのリソースに渡す。

上記ツールの比較については、以下の記事を参照してください。

※参考ドキュメント:

External Secretsとは

このうちExternal Secretsは、以下のような特徴を備えたツールです。

外部の秘匿情報管理サービスから情報を取得

External Secretsは、クラウド上のサービスや、秘匿情報管理ツールなどから情報を取得し、それを用いてKubernetes上でSecretリソースを作成します。秘匿情報の保管はKubernetes以外の外部サービスを利用することで、Kubernetes向けのマニフェストファイル内に直接データを設定する必要がなくなります。

またExternal Secretsはデフォルトで10sごとに外部サービスを確認し、外部サービスの設定が変更された場合、その変更をSecretに反映します。そのため、Kubernetes側での設定変更は不要となります。

※任意のタイミングでSecretを更新したい場合は、以下のリンク先を参照ください。

ExternalSecret リソースの利用

External SecretsではExternalSecretという独自リソースをCRDで定義し利用します。ExternalSecretを作成すると、External Secretsの起動したPodから、指定した外部サービスにアクセスし、データを取得します。取得後はそのデータを用いてSecretリソースを作成することで、Kubernetes上のリソースから秘匿情報を利用することが可能になります。

複数のクラウドプロバイダーに対応

External Secretsは現在以下のサービス・製品に対応しています。複数のクラウドプロバイダーに対応しているため、様々な環境に対して共通利用できることが期待できます。

※External Secrets概要図(GitHubより)

external-secret-summary

External Secretsを使ってみる

ここからは実際にExternal Secretsを利用する方法を紹介します。利用自体はとてもシンプルなので、公式のリポジトリにある手順に従って操作します。

検証環境

今回の検証は以下の環境で行いました。

Secrets Managerへのアクセス権限を付与

External Secretsは、Controllerが起動したPodから、外部サービスにアクセスしてデータを取得します。そのため、Podが対象のサービスにアクセスするためのアクセス権限を、クラスターまたはPodに付与する必要があります。ここではEKSクラスターのData Planeに対して、Secrets ManagerへのReadWrite権限を付与します。

eksctlコマンドを使用してEKSクラスターを作成すると、EKSを利用するために必要なIAMロールがデフォルトで付与されるので、AWSコンソール等から対象のロールに対してポリシーを追加することができます。

またEKSクラスター作成時にIAMポリシーを指定することも可能です。今回は以下のようなマニフェストファイルを指定してクラスターを作成しました。

apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
  name: eks-cluster
  region: ap-northeast-1
vpc:
  id: "vpc-xxxxxxxxxxxxxxxx"
  subnets:
    public:
      ap-northeast-1a: { id: subnet-xxxxxxxxxxxxxxxx }
      ap-northeast-1c: { id: subnet-xxxxxxxxxxxxxxxx }
      ap-northeast-1d: { id: subnet-xxxxxxxxxxxxxxxx }


nodeGroups:
- name: eks-nodegroup-1
  instanceType: t2.medium
  desiredCapacity: 2
  minSize: 2
  maxSize: 5
  securityGroups:
    attachIDs: [sg-xxxxxxxxxxxxxxxx]
  targetGroupARNs: 
    - arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy
    - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly
    - arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy
    - arn:aws:iam::aws:policy/SecretsManagerReadWrite  # 新規追加

External Secretsのデプロイ

続いてExternal Secretsをデプロイします。デプロイはKubernetesのパッケージ管理ツールであるHelmを利用します。今回はhelm templateを利用して必要なマニフェストファイルを生成し、それらを利用することでデプロイを行いました。

$ git clone https://github.com/external-secrets/kubernetes-external-secrets.git
$ cd kubernetes-external-secrets
$ helm template --output-dir ./testdir ./charts/kubernetes-external-secrets/
wrote ./testdir/kubernetes-external-secrets/templates/serviceaccount.yaml
wrote ./testdir/kubernetes-external-secrets/templates/rbac.yaml
wrote ./testdir/kubernetes-external-secrets/templates/rbac.yaml
wrote ./testdir/kubernetes-external-secrets/templates/rbac.yaml
wrote ./testdir/kubernetes-external-secrets/templates/service.yaml
wrote ./testdir/kubernetes-external-secrets/templates/deployment.yaml

生成されたマニフェストファイルは以下の通りです。Helmチャートのvalues.yamlで指定する必要のあるパラメータがRELEASE-NOTE-と表記されており、これを適宜修正しないとデプロイに失敗します。

$ kubectl apply -f ./testdir/kubernetes-external-secrets/templates/deployment.yaml

The Deployment "RELEASE-NAME-kubernetes-external-secrets" is invalid:
* metadata.name: Invalid value: "RELEASE-NAME-kubernetes-external-secrets": a DNS-1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')
* spec.template.spec.serviceAccountName: Invalid value: "RELEASE-NAME-kubernetes-external-secrets": a DNS-1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')

修正は手動のほか、オプションを指定することで生成時に指定することも可能です。 ただ私が試した限り--generate-nameオプションを追加してもRELEASE-NOTE-が変更されなかったため、そちらは手動で修正しました(こちらのIssueと関連する?)

serviceaccount.yaml

---
# Source: kubernetes-external-secrets/templates/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: RELEASE-NAME-kubernetes-external-secrets  # 要修正
  namespace: "default"
  labels:
    app.kubernetes.io/name: kubernetes-external-secrets
    helm.sh/chart: kubernetes-external-secrets-6.0.0
    app.kubernetes.io/instance: RELEASE-NAME  # 要修正
    app.kubernetes.io/managed-by: Helm

rbac.yaml

---
# Source: kubernetes-external-secrets/templates/rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  name: RELEASE-NAME-kubernetes-external-secrets  # 要修正
  labels:
    app.kubernetes.io/name: kubernetes-external-secrets
    helm.sh/chart: kubernetes-external-secrets-6.0.0
    app.kubernetes.io/instance: RELEASE-NAME  # 要修正
    app.kubernetes.io/managed-by: Helm
rules:
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["create", "update"]
  - apiGroups: [""]
    resources: ["namespaces"]
    verbs: ["get", "watch", "list"]
  - apiGroups: ["apiextensions.k8s.io"]
    resources: ["customresourcedefinitions"]
    resourceNames: ["externalsecrets.kubernetes-client.io"]
    verbs: ["get", "update"]
  - apiGroups: ["kubernetes-client.io"]
    resources: ["externalsecrets"]
    verbs: ["get", "watch", "list"]
  - apiGroups: ["kubernetes-client.io"]
    resources: ["externalsecrets/status"]
    verbs: ["get", "update"]
  - apiGroups: ["apiextensions.k8s.io"]
    resources: ["customresourcedefinitions"]
    verbs: ["create"]
---
# Source: kubernetes-external-secrets/templates/rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: RELEASE-NAME-kubernetes-external-secrets  # 要修正
  labels:
    app.kubernetes.io/name: kubernetes-external-secrets
    helm.sh/chart: kubernetes-external-secrets-6.0.0
    app.kubernetes.io/instance: RELEASE-NAME  # 要修正
    app.kubernetes.io/managed-by: Helm
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: RELEASE-NAME-kubernetes-external-secrets  # 要修正
subjects:
  - name: RELEASE-NAME-kubernetes-external-secrets  # 要修正
    namespace: "default"
    kind: ServiceAccount
---
# Source: kubernetes-external-secrets/templates/rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: RELEASE-NAME-kubernetes-external-secrets-auth  # 要修正
  labels:
    app.kubernetes.io/name: kubernetes-external-secrets
    helm.sh/chart: kubernetes-external-secrets-6.0.0
    app.kubernetes.io/instance: RELEASE-NAME  # 要修正
    app.kubernetes.io/managed-by: Helm
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: system:auth-delegator
subjects:
- name: RELEASE-NAME-kubernetes-external-secrets  # 要修正
  namespace: "default"
  kind: ServiceAccount

service.yaml

---
# Source: kubernetes-external-secrets/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: RELEASE-NAME-kubernetes-external-secrets  # 要修正
  namespace: "default"
  labels:
    app.kubernetes.io/name: kubernetes-external-secrets
    helm.sh/chart: kubernetes-external-secrets-6.0.0
    app.kubernetes.io/instance: RELEASE-NAME  # 要修正
    app.kubernetes.io/managed-by: Helm
spec:
  selector:
    app.kubernetes.io/name: kubernetes-external-secrets
  ports:
    - protocol: TCP
      port: 3001
      name: prometheus
      targetPort: prometheus

deployment.yaml

---
# Source: kubernetes-external-secrets/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: RELEASE-NAME-kubernetes-external-secrets  # 要修正
  namespace: "default"
  labels:
    app.kubernetes.io/name: kubernetes-external-secrets
    helm.sh/chart: kubernetes-external-secrets-6.0.0
    app.kubernetes.io/instance: RELEASE-NAME  # 要修正
    app.kubernetes.io/managed-by: Helm
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: kubernetes-external-secrets
      app.kubernetes.io/instance: RELEASE-NAME  # 要修正
  template:
    metadata:
      labels:
        app.kubernetes.io/name: kubernetes-external-secrets
        app.kubernetes.io/instance: RELEASE-NAME  # 要修正
    spec:
      serviceAccountName: RELEASE-NAME-kubernetes-external-secrets  # 要修正
      containers:
        - name: kubernetes-external-secrets
          image: "godaddy/kubernetes-external-secrets:6.0.0"
          ports:
          - name: prometheus
            containerPort: 3001
          imagePullPolicy: IfNotPresent
          resources:
            {}
          env:
          - name: "AWS_DEFAULT_REGION"
            value: "us-west-2"
          - name: "AWS_REGION"
            value: "us-west-2"
          - name: "LOG_LEVEL"
            value: "info"
          - name: "LOG_MESSAGE_KEY"
            value: "msg"
          - name: "METRICS_PORT"
            value: "3001"
          - name: "POLLER_INTERVAL_MILLISECONDS"
            value: "10000"
          - name: "VAULT_ADDR"
            value: "http://127.0.0.1:8200"
          # Params for env vars populated from k8s secrets
      securityContext:
        runAsNonRoot: true
# オプションの指定例
$ helm template --output-dir ./testdir ./charts/kubernetes-external-secrets/ --set env.AWS_REGION="ap-northeast-1"
wrote ./testdir/kubernetes-external-secrets/templates/serviceaccount.yaml
wrote ./testdir/kubernetes-external-secrets/templates/rbac.yaml
wrote ./testdir/kubernetes-external-secrets/templates/rbac.yaml
wrote ./testdir/kubernetes-external-secrets/templates/rbac.yaml
wrote ./testdir/kubernetes-external-secrets/templates/service.yaml
wrote ./testdir/kubernetes-external-secrets/templates/deployment.yaml

$ cat testdir/kubernetes-external-secrets/templates/deployment.yaml | grep -3 AWS_REGION
          env:
          - name: "AWS_DEFAULT_REGION"
            value: "us-west-2"
          - name: "AWS_REGION"
            value: "ap-northeast-1"
          - name: "LOG_LEVEL"
            value: "info"

※参考ドキュメント:

デプロイ後は以下のようにリソースが作成されます。

# External Secretsデプロイ
$ kubectl apply -f ./output_dir/kubernetes-external-secrets/templates/
deployment.apps/kubernetes-external-secrets created
clusterrole.rbac.authorization.k8s.io/kubernetes-external-secrets created
clusterrolebinding.rbac.authorization.k8s.io/kubernetes-external-secrets created
clusterrolebinding.rbac.authorization.k8s.io/kubernetes-external-secrets-auth created
service/kubernetes-external-secrets created
serviceaccount/kubernetes-external-secrets created

# デプロイ後の確認
$ kubectl get pods
NAME                                           READY   STATUS    RESTARTS   AGE
kubernetes-external-secrets-59b8db67c5-9scxm   1/1     Running   0          44s

$ kubectl get service
NAME                          TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
kubernetes                    ClusterIP   172.20.0.1       <none>        443/TCP    35h
kubernetes-external-secrets   ClusterIP   172.20.177.142   <none>        3001/TCP   76s

AWS Secrets Managerへの登録

続いてAWS Secrets Managerのほうにテストデータを登録します。ここではAWS CLIからデータを登録します。

# 秘匿データの設定
$ aws secretsmanager create-secret --name hello-service/password --secret-string "5678"

# 作成後の確認
$ aws secretsmanager get-secret-value --secret-id hello-service/password
{
    "ARN": "arn:aws:secretsmanager:ap-northeast-1:111111111111:secret:hello-service/password-YmCbrt",
    "Name": "hello-service/password",
    "VersionId": "a298c3ea-1e27-4e05-b379-a13856e6f8c8",
    "SecretString": "5678",
    "VersionStages": [
        "AWSCURRENT"
    ],
    "CreatedDate": "2020-11-17T13:53:59.130000+09:00"
}

ExternalSecretリソースの作成

ここまででExternal Secretsを利用する準備が整ったので、ExternalSecretリソースをデプロイします。今回は以下のマニフェストファイルを利用しました。

hello-service-es.yaml

apiVersion: kubernetes-client.io/v1
kind: ExternalSecret
metadata:
  name: hello-service
spec:
  backendType: secretsManager
  region: ap-northeast-1
  data:
    - key: hello-service/password
      name: password
  # optional: specify a template with any additional markup you would like added to the downstream Secret resource.
  # This template will be deep merged without mutating any existing fields. For example: you cannot override metadata.name.
  template:
    metadata:
      annotations:
        cat: cheese
      labels:
        dog: farfel

上記ファイルを用いてリソースを作成します。以下の通り、ExternalSecretリソースを作成することで(外部データを参照したのちに)Secretリソースが作成されます。

# ExternalSecretリソースの作成
$ kubectl apply -f hello-service-es.yaml
externalsecret.kubernetes-client.io/hello-service created


# 作成後の確認
$ kubectl get externalsecret
NAME            LAST SYNC   STATUS    AGE
hello-service   7s          SUCCESS   7s

$ kubectl describe externalsecret hello-service
Name:         hello-service
Namespace:    default
Labels:       <none>
Annotations:  kubectl.kubernetes.io/last-applied-configuration:
                {"apiVersion":"kubernetes-client.io/v1","kind":"ExternalSecret","metadata":{"annotations":{},"name":"hello-service","namespace":"default"}...
API Version:  kubernetes-client.io/v1
Kind:         ExternalSecret
Metadata:
  Creation Timestamp:  2020-11-20T11:25:20Z
  Generation:          1
  Resource Version:    410381
  Self Link:           /apis/kubernetes-client.io/v1/namespaces/default/externalsecrets/hello-service
  UID:                 181d2db7-8162-4758-bb60-3b08c3fc4219
Spec:
  Backend Type:  secretsManager
  Data:
    Key:   hello-service/password
    Name:  password
  Region:  ap-northeast-1
Status:
  Last Sync:            2020-11-20T11:27:01.865Z
  Observed Generation:  1
  Status:               SUCCESS
Events:                 <none>



# Secretリソースが作成される
$ kubectl get secret
NAME                                      TYPE                                  DATA   AGE
default-token-88vp5                       kubernetes.io/service-account-token   3      35h
hello-service                             Opaque                                1      15s
kubernetes-external-secrets-token-v7prp   kubernetes.io/service-account-token   3      14m


$ kubectl get secret hello-service -o yaml
apiVersion: v1
data:
  password: NTY3OA==  # “5678”
kind: Secret
metadata:
  creationTimestamp: "2020-11-20T11:25:20Z"
  name: hello-service
  namespace: default
  ownerReferences:
  - apiVersion: kubernetes-client.io/v1
    controller: true
    kind: ExternalSecret
    name: hello-service
    uid: 181d2db7-8162-4758-bb60-3b08c3fc4219
  resourceVersion: "410070"
  selfLink: /api/v1/namespaces/default/secrets/hello-service
  uid: 4975494d-a2ce-4395-a4bb-cc79c7af8d04
type: Opaque

IAM Role for Service Account (IRSA)を利用する場合に必要な設定

ここまでの手順では、EKSクラスターのData Planeに対して、AWS Secrets ManagerへのReadWrite権限を付与していました。この設定ではExternalSecret Controller以外のPodもSecrets Managerにアクセスすることができてしまうため、セキュリティ的に問題となる場合があります。

AWSではIAM Role for Service Account (IRSA)という機能があります。これはKubernetes上で利用するアカウントであるService Accountに対してIAMロールを付与し、Pod起動時に特定のService Accountを指定することで、Pod単位での権限付与を実現する機能です。

irsa-diagram

AWS IAMは外部IDプロバイダーと連携することで、外部IDに対してAWSリソースへのアクセス権を付与することができます。IRSAでは、EKS上でのOpenID Connect(OIDC)プロバイダーを作成・有効化し、これを利用します。

一方KubernetesではVolumeリソースのprojectedプラグインを利用して、Service Account Tokenを利用することができます。projected VolumeはKubernetes Volumeリソースで利用できるプラグインの種類の一つで、Secret / ConfigMap / downwardAPI / Service Accoutn Tokenの4種類のリソースを1か所のディレクトリに集約してマウントする機能を提供します。このトークンにAWSへのアクセス権を付与することで、特定のService AccountがAWSリソースへアクセスすることを許可します。

Amazon EKS上でExternal Secretsを利用する際も、このIRSAを利用することで、必要最小限の権限付与に抑え、セキュリティを向上することが期待できます。

External Secretsを利用する場合、ExternalSecrets Controller Podを作成すると、Amazon EKS Pod Identity Webhookを介して、Podに必要な環境変数を追加します。この環境変数にはIAMロールのARNとトークンの情報が含まれ、Podに対してprojectedプラグインのVolumeを設定します。

次にExternalSecretリソースを作成すると、PodがAWSリソースにアクセスしようとし、OIDCプロバイダーに対するトークンの検証をSTS経由で行います。認証に成功すればアクセス権を付与(ここではAWS Secrets Managerへのアクセス)し、AWS Secrets Managerに保管された秘匿データを取得、Secretリソースの作成を行うことができます。

※External SecretsをIRSAで利用する場合の簡略図。図中の数字は処理のステップ順を表す。

f:id:FY0323:20201218232106j:plain

IRSAを利用するには、以下のような操作が必要となります。なお、以降の作業は、AWS Secrets Managerへのアクセスを許可するIAMロールが無い状況を想定していますので、必要があればEKSクラスターから指定のロールの削除を実行します。

OpenID Connectプロバイダーの有効化

IRSAを利用するには、まずクラスター上でOpenID Connect (OIDC) IDプロバイダーを有効にする必要があります。IDプロバイダーを有効にするにはeksctl utils associate-iam-oidc-providerコマンドを実行します。

$ eksctl utils associate-iam-oidc-provider --name eks-cluster --approve
Flag --name has been deprecated, use --cluster
[]  eksctl version 0.29.2
[]  using region ap-northeast-1
[]  will create IAM Open ID Connect provider for cluster "eks-cluster" in "ap-northeast-1"
[]  created IAM Open ID Connect provider for cluster "eks-cluster" in "ap-northeast-1"

# 有効化後の確認
$ aws iam list-open-id-connect-providers
{
    "OpenIDConnectProviderList": [
        {
            "Arn": "arn:aws:iam::111111111111:oidc-provider/oidc.eks.ap-northeast-1.amazonaws.com/id/11111111111111111111111111111111"
        }
    ]
}

IAMロールの作成

次にService Accountに付与するIAMロールを用意します。今回はAWS Secrets Managerへアクセスするために、以下のようなCloudFormationテンプレートを利用してIAMロールを作成します。

AWSTemplateFormatVersion: 2010-09-09
Resources:
  eksirsa:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Federated: 
              - arn:aws:iam::111111111111:oidc-provider/oidc.eks.ap-northeast-1.amazonaws.com/id/11111111111111111111111111111111
            Action:
              - 'sts:AssumeRoleWithWebIdentity'
            Condition:
              StringEquals:         
                oidc.eks.ap-northeast-1.amazonaws.com/id/11111111111111111111111111111111:sub: system:serviceaccount:default:kubernetes-external-secrets
                oidc.eks.ap-northeast-1.amazonaws.com/id/11111111111111111111111111111111:aud: sts.amazonaws.com
      Path: /
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/SecretsManagerReadWrite
      RoleName: eks-irsa
      Tags:
        - Key: alpha.eksctl.io/cluster-name
          Value: eks-cluster
        - Key: eksctl.cluster.k8s.io/v1alpha1/cluster-name
          Value: eks-cluster
        - Key: alpha.eksctl.io/iamserviceaccount-name
          Value: default/kubernetes-external-secrets
        - Key: alpha.eksctl.io/eksctl-version
          Value: 0.29.2
# エラーが発生する場合は”--capabilities CAPABILITY_NAMED_IAM”を追加
$ aws cloudformation deploy --stack-name eks-irsa --template aws-iam-irsa.yaml --capabilities CAPABILITY_NAMED_IAM

Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - eks-irsa

# 作成後の確認
$ aws iam get-role --role-name eks-irsa
{
    "Role": {
        "Path": "/",
        "RoleName": "eks-irsa",
        "RoleId": "AROATYRF5FWUEKGK4AIKJ",
        "Arn": "arn:aws:iam::111111111111:role/eks-irsa",
        "CreateDate": "2020-12-12T23:05:21+00:00",
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Effect": "Allow",
                    "Principal": {
                        "Federated": "arn:aws:iam::111111111111:oidc-provider/oidc.eks.ap-northeast-1.amazonaws.com/id/11111111111111111111111111111111"
                    },
                    "Action": "sts:AssumeRoleWithWebIdentity",
                    "Condition": {
                        "StringEquals": {
                            "oidc.eks.ap-northeast-1.amazonaws.com/id/11111111111111111111111111111111:aud": "sts.amazonaws.com",
                            "oidc.eks.ap-northeast-1.amazonaws.com/id/11111111111111111111111111111111:sub": "system:serviceaccount:default:kubernetes-external-secrets"
                        }
                    }
                }
            ]
        },
        "Description": "",
        "MaxSessionDuration": 3600,
        "Tags": [
            {
                "Key": "alpha.eksctl.io/cluster-name",
                "Value": "eks-cluster"
            },
            {
                "Key": "eksctl.cluster.k8s.io/v1alpha1/cluster-name",
                "Value": "eks-cluster"
            },
            {
                "Key": "alpha.eksctl.io/iamserviceaccount-name",
                "Value": "default/kubernetes-external-secrets"
            },
            {
                "Key": "alpha.eksctl.io/eksctl-version",
                "Value": "0.29.2"
            }
        ],
        "RoleLastUsed": {}
    }
}

Service Accountに設定を追加

次に、作成したIAMロールをService Accountで利用するよう、設定を変更します。

---
# Source: kubernetes-external-secrets/templates/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: kubernetes-external-secrets
  namespace: "default"
  labels:
    app.kubernetes.io/name: kubernetes-external-secrets
    helm.sh/chart: kubernetes-external-secrets-6.0.0
    app.kubernetes.io/instance: 6.0.0
    app.kubernetes.io/managed-by: Helm
  # 以降の記述を追加
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::111111111111:role/eks-irsa
# Service Account更新
$ kubectl apply -f output_dir/kubernetes-external-secrets/templates/serviceaccount.yaml
serviceaccount/kubernetes-external-secrets configured


# 更新後はAnnotationsが付与されている
$ kubectl get sa
NAME                          SECRETS   AGE
default                       1         6d22h
kubernetes-external-secrets   1         6d21h

$ kubectl describe sa kubernetes-external-secrets
Name:                kubernetes-external-secrets
Namespace:           default
Labels:              app.kubernetes.io/instance=6.0.0
                     app.kubernetes.io/managed-by=Helm
                     app.kubernetes.io/name=kubernetes-external-secrets
                     helm.sh/chart=kubernetes-external-secrets-6.0.0
Annotations:         eks.amazonaws.com/role-arn: arn:aws:iam::111111111111:role/eks-irsa
                     kubectl.kubernetes.io/last-applied-configuration:
                       {"apiVersion":"v1","kind":"ServiceAccount","metadata":{"annotations":{"eks.amazonaws.com/role-arn":"arn:aws:iam::111111111111:role/eks-irs...
Image pull secrets:  <none>
Mountable secrets:   kubernetes-external-secrets-token-wggsc
Tokens:              kubernetes-external-secrets-token-wggsc
Events:              <none>

Deploymentリソースの設定変更

これでIAMロールを付与したService Accountを利用できるようになったので、Deploymentなどのリソースを作成します。

Deploymentリソース作成後に内容を確認すると、aws-iam-tokenという名称のProjected Volumeが付与された様子が確認できます。

# Deployment再作成
$ kubectl apply -f output_dir/kubernetes-external-secrets/templates/deployment.yaml
deployment.apps/kubernetes-external-secrets configured


# 作成後の確認
$ kubectl get pods
NAME                                           READY   STATUS        RESTARTS   AGE
kubernetes-external-secrets-59859579b6-xqkg4   1/1     Running       0          7s
kubernetes-external-secrets-59b8db67c5-r74k8   0/1     Terminating   0          6d21h


$ kubectl describe pod kubernetes-external-secrets-59859579b6-xqkg4
Name:         kubernetes-external-secrets-59859579b6-xqkg4
Namespace:    default
Priority:     0
Node:         ip-10-0-3-64.ap-northeast-1.compute.internal/10.0.3.64
Start Time:   Sun, 13 Dec 2020 08:10:52 +0900
Labels:       app.kubernetes.io/instance=6.0.0
              app.kubernetes.io/name=kubernetes-external-secrets
              pod-template-hash=59859579b6
Annotations:  kubernetes.io/psp: eks.privileged
Status:       Running
IP:           10.0.3.247
IPs:
  IP:           10.0.3.247
Controlled By:  ReplicaSet/kubernetes-external-secrets-59859579b6
Containers:
  kubernetes-external-secrets:
    Container ID:   docker://f6813c88c87e065be47fa29b1de1eb2434928eeb30f866137034212832d8bdf2
    Image:          godaddy/kubernetes-external-secrets:6.0.0
    Image ID:       docker-pullable://godaddy/kubernetes-external-secrets@sha256:e827f7bc272117a97a8562ac70e37810266467f07d2fce798c8abc360b892c08
    Port:           3001/TCP
    Host Port:      0/TCP
    State:          Running
      Started:      Sun, 13 Dec 2020 08:10:53 +0900
    Ready:          True
    Restart Count:  0
    Environment:
      AWS_DEFAULT_REGION:            ap-northeast-1
      AWS_REGION:                    ap-northeast-1
      LOG_LEVEL:                     info
      LOG_MESSAGE_KEY:               msg
      METRICS_PORT:                  3001
      POLLER_INTERVAL_MILLISECONDS:  10000
      VAULT_ADDR:                    http://127.0.0.1:8200
      AWS_ROLE_ARN:                  arn:aws:iam::111111111111:role/eks-irsa
      AWS_WEB_IDENTITY_TOKEN_FILE:   /var/run/secrets/eks.amazonaws.com/serviceaccount/token
    Mounts:
      /var/run/secrets/eks.amazonaws.com/serviceaccount from aws-iam-token (ro)
      /var/run/secrets/kubernetes.io/serviceaccount from kubernetes-external-secrets-token-wggsc (ro)
Conditions:
  Type              Status
  Initialized       True
  Ready             True
  ContainersReady   True
  PodScheduled      True
Volumes:
  aws-iam-token:  # 追加されている
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  86400
  kubernetes-external-secrets-token-wggsc:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  kubernetes-external-secrets-token-wggsc
    Optional:    false
QoS Class:       BestEffort
Node-Selectors:  <none>
Tolerations:     node.kubernetes.io/not-ready:NoExecute for 300s
                 node.kubernetes.io/unreachable:NoExecute for 300s
Events:
  Type    Reason     Age   From                                                   Message
  ----    ------     ----  ----                                                   -------
  Normal  Scheduled  3s    default-scheduler                                      Successfully assigned default/kubernetes-external-secrets-59859579b6-xqkg4 to ip-10-0-3-64.ap-northeast-1.compute.internal
  Normal  Pulled     2s    kubelet, ip-10-0-3-64.ap-northeast-1.compute.internal  Container image "godaddy/kubernetes-external-secrets:6.0.0" already present on machine
  Normal  Created    2s    kubelet, ip-10-0-3-64.ap-northeast-1.compute.internal  Created container kubernetes-external-secrets
  Normal  Started    2s    kubelet, ip-10-0-3-64.ap-northeast-1.compute.internal  Started container kubernetes-external-secrets

これでExternalSecretリソースが作成できるかと思いましたが、この状態でリソースを作成しようとすると、以下のようなエラーが発生し、Secrets Managerからデータを取得することができませんでした。

$ kubectl apply -f hello-service-es.yaml
externalsecret.kubernetes-client.io/hello-service created

$ kubectl get externalsecret
NAME            LAST SYNC   STATUS
                                                                                                                                                 AGE
hello-service   5s          ERROR, User: arn:aws:sts::111111111111:assumed-role/eksctl-eks-cluster-nodegroup-eks-NodeInstanceRole-1R5YXUU3GJMP5/i-09defe966bee6cb58 is not authorized
to perform: secretsmanager:GetSecretValue on resource: arn:aws:secretsmanager:ap-northeast-1:111111111111:secret:hello-service/password-YmCbrt   6s

エラーメッセージを見る限り、EKSクラスターのNodeGroupに対して付与されるIAMロールを利用しようとしているように見えます。ExternalSecretリソースの定義では、利用するARNを指定することもできるため、こちらを変更してみます。

apiVersion: kubernetes-client.io/v1
kind: ExternalSecret
metadata:
  name: hello-service
spec:
  backendType: secretsManager
  # optional: specify role to assume when retrieving the data
  roleArn: arn:aws:iam::111111111111:role/eks-irsa
  region: ap-northeast-1
  data:
    - key: hello-service/password
      name: password
  # optional: specify a template with any additional markup you would like added to the downstream Secret resource.
  # This template will be deep merged without mutating any existing fields. For example: you cannot override metadata.name.
  template:
    metadata:
      annotations:
        cat: cheese
      labels:
        dog: farfel

上記ファイルを用いてリソースを作成すると、メッセージは変更されたものの、やはりエラーメッセージが確認できます。

# 作成済みのリソースを一度削除する
$ kubectl delete -f hello-service-es.yaml
externalsecret.kubernetes-client.io "hello-service" deleted


# 再作成
$ kubectl apply -f hello-service-es.yaml
externalsecret.kubernetes-client.io/hello-service created


# 作成後の確認
$ kubectl get externalsecret
NAME            LAST SYNC   STATUS                                 AGE
hello-service   4s          ERROR, Missing credentials in config   4s

いろいろと調べてみたところ、External SecretsでIRSAを利用するには、DeploymentのSecurity Cntextの設定を変更し、fsGroupを以下のように変更する必要があるようです。

(中略)

securityContext:
  runAsNonRoot: true
  fsGroup: 65534

こちらの情報は、現在一緒にお仕事をさせて頂いている方から教えて頂きました。この場を借りて感謝申し上げます。

※参考リンク:

上記設定を追加したところ、無事にデータを取得し、Secretリソースが作成されることを確認できました。

# 設定変更後、Controllerを再作成

$ kubectl apply -f output_dir/kubernetes-external-secrets/templates/deployment.yaml
deployment.apps/kubernetes-external-secrets created


# 作成後の確認
$ kubectl get deployment kubernetes-external-secrets -o yaml

(中略)

      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext:
        fsGroup: 65534  ★
        runAsNonRoot: true
      serviceAccount: kubernetes-external-secrets
      serviceAccountName: kubernetes-external-secrets
      terminationGracePeriodSeconds: 30

(中略)



# ExternalSecretリソースの作成
$ kubectl apply -f hello-service-es.yaml
externalsecret.kubernetes-client.io/hello-service created


# 作成後の確認
$ kubectl get externalsecret
NAME            LAST SYNC   STATUS    AGE
hello-service   5s          SUCCESS   8s

$ kubectl get secret
NAME                                      TYPE                                  DATA   AGE
default-token-875qc                       kubernetes.io/service-account-token   3      6d22h
hello-service                             Opaque                                1      14s
kubernetes-external-secrets-token-wggsc   kubernetes.io/service-account-token   3      6d21h

$ kubectl get secret hello-service -o yaml
apiVersion: v1
data:
  password: NTY3OA==  # “5678”
kind: Secret
metadata:
  creationTimestamp: "2020-12-12T23:27:03Z"
  name: hello-service
  namespace: default
  ownerReferences:
  - apiVersion: kubernetes-client.io/v1
    controller: true
    kind: ExternalSecret
    name: hello-service
    uid: 44e6b1f7-3c91-4a7c-b316-a2cec040a114
  resourceVersion: "1762644"
  selfLink: /api/v1/namespaces/default/secrets/hello-service
  uid: 34b88216-3814-4d32-873c-f10103504d75
type: Opaque

参考ドキュメント

【メモ】 ArgoCDでAWS ALBを利用するときの設定

はじめに

先日ArgoCDをAmazon EKS上にデプロイし、AWS ALB経由でArgoCD UIにアクセスしようとした際、幾つかの設定変更が必要であることが分かったので、備忘録として残しておきます。

ArgoCD - AWS ALBの構成

ArgoCD + AWS ALBの構成は、大まかには以下のようになります。ArgoCDのWeb UIはArgoCD Serverが提供します。AWS ALBはNodePortを経由してArgoCD Serverにアクセスし、Web UIへの接続を実現します。

f:id:FY0323:20201115120103p:plain

このため、AWS ALBからArgoCDにアクセスするには、argocd-server というCluster IPをNode Portに変更することでアクセスできそうです。

ArgoCDデプロイ時にNodePortが作成されるよう、試しに以下のようなyamlファイルを用意します。ここではtype: NodePortに変更し、spec.nodePortを追加しています。

---
apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/component: server
    app.kubernetes.io/name: argocd-server
    app.kubernetes.io/part-of: argocd
  name: argocd-server
spec:
  type: NodePort
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 8080
    nodePort: 30080
  - name: https
    port: 443
    protocol: TCP
    targetPort: 8080
    nodePort: 30443
  selector:
    app.kubernetes.io/name: argocd-server

元としたマニフェストファイル: GitHub: argoproj/argo-cd

またAWS ALBは以下のようなCloudFrmationファイルを用意し、作成しました。

AWSTemplateFormatVersion: '2010-09-09'
Description: ALB for ArgoCD Server

Parameters:
  ALBName:
    Type: String
    Default: "eks-yamaji-alb"
  TargetGroupPort:
    Type: String
    Default: "30080"
  Scheme:
    Type: String
    Default: "internet-facing"

Resources:
  ListenerHttp:
    Properties:
      DefaultActions:
        - Type: "forward"
          TargetGroupArn: !Ref "TargetGroup"
      LoadBalancerArn: !Ref "LoadBalancer"
      Port: 80
      Protocol: HTTP
    Type: AWS::ElasticLoadBalancingV2::Listener
  LoadBalancer:
    Properties:
      Name: !Sub ${ALBName}
      SecurityGroups:
        - !Ref "SecurityGroup"
      Subnets:
        - "subnet-xxxxxxxxxxxxxxxx"
      Scheme: !Sub ${Scheme}
      Type: "application"
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
  SecurityGroup:
    Properties:
      GroupDescription: !Sub ${ALBName}-sg
      SecurityGroupIngress:
        - CidrIp: "local client ip address"
          FromPort: 80
          IpProtocol: tcp
          ToPort: 80
        - CidrIp: "10.0.0.0/16"
          FromPort: 30080
          IpProtocol: tcp
          ToPort: 30080
      VpcId: "vpc-xxxxxxxxxxxxxxxx"
    Type: AWS::EC2::SecurityGroup
  TargetGroup:
    Properties:
      Name: !Sub ${ALBName}-tg
      Port: !Sub ${TargetGroupPort}
      Protocol: HTTP
      VpcId: "vpc-xxxxxxxxxxxxxxxx"
    Type: AWS::ElasticLoadBalancingV2::TargetGroup

上記2つのYamlファイルを利用してリソースを作成し、セキュリティグループの設定変更と、ターゲットグループの追加を行いました。

設定としては以上で接続されるはずですが、以下のようにALBからターゲットグループへのヘルスチェックで307メッセージが表示され、失敗します。

f:id:FY0323:20201115120158p:plain

なお、ALBのヘルスチェックの設定を色々と変更しましたが、ヘルスチェックが失敗する状況は改善されませんでした。

ArgoCDでAWS ALBを利用する設定

ArgoCDの公式ドキュメントには、ArgoCD Serverへアクセスした際の動きが記載されています。ArgoCD Serverへのアクセスは、デフォルトでTLSを有効化するよう、HTTP (80)宛ての通信はHTTPS (443)へと転送されます。

この結果、ヘルスチェックでPort: 80にアクセスしたトラフィックPort: 443へと転送され、その結果として上の画像のようなメッセージが表示されることがわかります。

ArgoCD設定内容

ArgoCD Serverは、コンテナ起動時に--insecureオプションを付与することで、TLSを無効化して起動することができます。--insecureオプションを付与すると、デフォルトのような転送処理を無効化することができます。ここでは以下の例のように、ArgoCDインストール時のマニフェストファイルを修正します。

参考:GitHub: argoproj/argo-cd

---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app.kubernetes.io/component: server
    app.kubernetes.io/name: argocd-server
    app.kubernetes.io/part-of: argocd
  name: argocd-server
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: argocd-server
  template:
    metadata:
      labels:
        app.kubernetes.io/name: argocd-server
    spec:
      containers:
      - command:
        - argocd-server
        - --staticassets
        - /shared/app
        - --insecure # このオプションを追加
        image: argoproj/argocd:latest
        imagePullPolicy: Always
        name: argocd-server
        ports:
        - containerPort: 8080
        - containerPort: 8083
        readinessProbe:
          httpGet:
            path: /healthz
            port: 8080
          initialDelaySeconds: 3
          periodSeconds: 30
        volumeMounts:
        - mountPath: /app/config/ssh
          name: ssh-known-hosts
        - mountPath: /app/config/tls
          name: tls-certs
      serviceAccountName: argocd-server
      volumes:
      - emptyDir: {}
        name: static-files
      - configMap:
          name: argocd-ssh-known-hosts-cm
        name: ssh-known-hosts
      - configMap:
          name: argocd-tls-certs-cm
        name: tls-certs

上記のように修正することで転送は抑制され、結果としてヘルスチェックが成功するようになります。

f:id:FY0323:20201115120314p:plain

ArgoCD CLIでのアクセス方法

ArgoCD Serverはargocdコマンドを実行した際のアクセス先としても機能します。argocdコマンドはアクセス時にgRPCを利用しており、ArgoCD ServerはWeb UIへのアクセスとargocdコマンドの接続先に同じPort: 443を利用しています。

ArgoCD Server起動時に--insecureオプションを利用していると、Port: 443への転送が無効化され、gRPCが利用できず、ログインができなくなるという問題があります。

$ argocd login --insecure albdomain.ap-northeast-1.elb.amazonaws.com:80
WARNING: server is not configured with TLS. Proceed (y/n)? y
Username: admin
Password:
FATA[0009] rpc error: code = Unavailable desc = transport is closing

これを解消するには、argocdコマンドのオプションの一つである--grpc-webを利用することができます。このオプションを指定することでgRPCを利用し、CLI経由でアクセスすることができます。

$ argocd login --insecure albdomain.ap-northeast-1.elb.amazonaws.com:80 --grpc-web
WARNING: server is not configured with TLS. Proceed (y/n)? y
Username: admin
Password:
'admin' logged in successfully
Context 'albdomain.ap-northeast-1.elb.amazonaws.com:80' updated

なおargocd loginコマンドのオプションは以下の通りです。

$ argocd login --help
Log in to Argo CD
Usage:
 argocd login SERVER [flags]
Flags:
 -h, --help              help for login
     --name string       name to use for the context
     --password string   the password of an account to authenticate
     --sso               perform SSO login
     --sso-port int      port to run local OAuth2 login application (default 8085)
     --username string   the username of an account to authenticate
Global Flags:
     --auth-token string               Authentication token
     --client-crt string               Client certificate file
     --client-crt-key string           Client certificate key file
     --config string                   Path to Argo CD config (default "/home/testadm/.argocd/config")
     --grpc-web                        Enables gRPC-web protocol. Useful if Argo CD server is behind proxy which does not support HTTP2.
     --grpc-web-root-path string       Enables gRPC-web protocol. Useful if Argo CD server is behind proxy which does not support HTTP2. Set web root.
 -H, --header strings                  Sets additional header to all requests made by Argo CD CLI. (Can be repeated multiple times to add multiple headers, also supports comma separated headers)
     --insecure                        Skip server certificate and domain verification
     --logformat string                Set the logging format. One of: text|json (default "text")
     --loglevel string                 Set the logging level. One of: debug|info|warn|error (default "info")
     --plaintext                       Disable TLS
     --port-forward                    Connect to a random argocd-server port using port forwarding
     --port-forward-namespace string   Namespace name which should be used for port forwarding
     --server string                   Argo CD server address
     --server-crt string               Server certificate file

参考ドキュメント

Argo CD Notificationsを試す

はじめに

今回は、Argo CDと組み合わせて利用することのできる通知ツールの一つ、Argo CD Notificationsについて、概要と利用方法を紹介します。

Argo CD Notificationsとは

Argo CD Notificationsは、Argo CDのリソースを監視し、実行結果に応じてメール・Slackなどで通知してくれるプロダクトです。Triggerによって通知を送る契機やタイミングを指定、Templateによって通知する内容やフォーマットを指定することが可能です。また、Argo CD Notificationsが監視対象とするのはApplication AppProjectで、対象のリソースに対してAnnotationを付与することで監視対象とすることができます。

他の通知ツールとの比較

Argo CDのNotificationページには、Argo CD Notificationsのほか、2つのプロダクトが紹介されています。

  • Argo Kube Notifier: KubernetesリソースをモニタリングするControllerを設置し、指定したルールと一致した場合は通知を送ることができます。Argo CD Notificationsと同じくargoproj-labsの配下にありますが、半年ほど前から開発は止まっているように見えます。

  • Kube Watch: Kubernetesクラスターをモニタリングしリソースの変更が走った場合はWebhookを通じて通知を送付します。Dockerでの軌道のほか、kubewatchコマンドを利用することでローカル環境で起動することができます。Slackを利用する場合はSlack botを利用する必要があります。

Argo CD Notificationsを利用する

ここからは実際にArgo CD Notificationsを利用していきます。今回はArgo CDのイベントに応じてSlackへ通知を飛ばす方法を見ていきます。

検証環境

今回の検証環境は以下の通りです。

Argo CD Notificationsのインストール

まずはArgo CD Notificationsをクラスター上にデプロイします。

# Namespaceの用意
$ kubectl get ns
NAME              STATUS   AGE
default           Active   12m
kube-node-lease   Active   12m
kube-public       Active   12m
kube-system       Active   12m

$ kubectl create namespace argocd
namespace/argocd created

$ kubectl get ns
NAME              STATUS   AGE
argocd            Active   2s
default           Active   12m
kube-node-lease   Active   13m
kube-public       Active   13m
kube-system       Active   13m


# Argo CD Notificationsのデプロイ
$ kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj-labs/argocd-notifications/stable/manifests/install.yaml
serviceaccount/argocd-notifications-controller created
role.rbac.authorization.k8s.io/argocd-notifications-controller created
rolebinding.rbac.authorization.k8s.io/argocd-notifications-controller created
configmap/argocd-notifications-cm created
secret/argocd-notifications-secret created
deployment.apps/argocd-notifications-controller created

# デプロイ後の確認
$ kubectl get -n argocd sa
NAME                              SECRETS   AGE
argocd-notifications-controller   1         17s
default                           1         26s

$ kubectl get -n argocd role
NAME                              AGE
argocd-notifications-controller   25s

$ kubectl get -n argocd rolebinding
NAME                              AGE
argocd-notifications-controller   30s

$ kubectl get -n argocd cm
NAME                      DATA   AGE
argocd-notifications-cm   0      37s

$ kubectl get -n argocd secret
NAME                                          TYPE                                  DATA   AGE
argocd-notifications-controller-token-cvqwg   kubernetes.io/service-account-token   3      43s
argocd-notifications-secret                   Opaque                                0      43s
default-token-tdxr6                           kubernetes.io/service-account-token   3      52s

$ kubectl get -n argocd all
NAME                                                  READY   STATUS    RESTARTS   AGE
pod/argocd-notifications-controller-64664db97-psnkx   1/1     Running   0          57s

NAME                                              READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/argocd-notifications-controller   1/1     1            1           58s

NAME                                                        DESIRED   CURRENT   READY   AGE
replicaset.apps/argocd-notifications-controller-64664db97   1         1         1       58s

Slackの設定

次にSlackのほうの設定を行います。設定はArgo CD Notificationsのドキュメントに従って行います。

まずSlackのこちらのページから、対象のWorkspaceを選択してアプリケーションを作成します。

f:id:FY0323:20201028083554p:plain

アプリケーションの作成ができたら、次に左のメニューからOAuth &Permissionsを選択します。

f:id:FY0323:20201028083710p:plain

次にアプリケーションの実行できる権限を指定するため、Scopeの項目へ移動し、Bot Token Scopesでchat:writeを選択します。

f:id:FY0323:20201028084140p:plain

Scopeを設定したら、次の画面上でInstall App to Workspaceを選択します。

f:id:FY0323:20201028083947p:plain

アプリケーションがWorkspaceにアクセスする権限をリクエストするので、Allowを選択します。

f:id:FY0323:20201028084322p:plain

次の画面で、発行されたアクセストークンを確認できます。後ほどクラスター上のSecretリソースにこのトークン情報を設定するため、これをコピーしておきます。

f:id:FY0323:20201028084554p:plain

また、後ほど通知が飛ぶのを確認するため、テスト用のチャンネルを作成(今回はtest-argocd-notificationというチャンネルを用意)し、テスト用のチャンネルにアプリを追加しておきます。

Argo CD Notificationsの設定

次にArgo CD Notificationsの設定を行います。先ほど表示されたアクセストークンをargocd-notifications-secretというSecretリソースに設定します。今回は以下のようにマニフェストファイルを用意し、すでに作成されたargocd-notifications-secretから上書きします。

argocd-notification-secret.yaml

apiVersion: v1
kind: Secret
metadata:
  name: argocd-notifications-secret
  namespace: argocd
stringData:
  notifiers.yaml: |
    slack:
      token: <my-token>
type: Opaque

また、Argo CD Notificationsを利用するにはargocd-notifications-cmというConfigMapに、通知条件と通知時のテンプレートを指定する必要があります。

ここでは、以下のようなマニフェストファイルを利用します。Argo CD Notificationsにはあらかじめ利用可能なTrigger/Templateが用意されており、ここではon-sync-succeededというTriggerを指定しています。

argocd-notification-cm.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-notifications-cm
  namespace: argocd
data:
  config.yaml: |
    triggers:
      - name: on-sync-succeeded
        enabled: true

上記2つのマニフェストファイルを利用して設定を行います。

$ cd argocd-notification
$ kubectl apply -f argocd-notification-secret.yaml
secret/argocd-notifications-secret configured

$ kubectl apply -f argocd-notification-cm.yaml
configmap/argocd-notifications-cm configured


# 変更後の確認
$ kubectl get secret -n argocd
NAME                                          TYPE                                  DATA   AGE
argocd-notifications-controller-token-cvqwg   kubernetes.io/service-account-token   3      23m
argocd-notifications-secret                   Opaque                                1      23m
default-token-tdxr6                           kubernetes.io/service-account-token   3      23m

$ kubectl get cm -n argocd
NAME                      DATA   AGE
argocd-notifications-cm   1      23m

Argo CDのデプロイとSlcakへの通知

ここまででArgo CD Notificationsの通知準備ができたので、ここからはArgo CDの利用準備を進めます。

まずはArgo CDをデプロイします。デプロイや設定などは、以前の記事などをご覧ください。

$ kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
customresourcedefinition.apiextensions.k8s.io/applications.argoproj.io created
customresourcedefinition.apiextensions.k8s.io/appprojects.argoproj.io created
serviceaccount/argocd-application-controller created
serviceaccount/argocd-dex-server created
serviceaccount/argocd-server created
role.rbac.authorization.k8s.io/argocd-application-controller created
role.rbac.authorization.k8s.io/argocd-dex-server created
role.rbac.authorization.k8s.io/argocd-server created
clusterrole.rbac.authorization.k8s.io/argocd-application-controller created
clusterrole.rbac.authorization.k8s.io/argocd-server created
rolebinding.rbac.authorization.k8s.io/argocd-application-controller created
rolebinding.rbac.authorization.k8s.io/argocd-dex-server created
rolebinding.rbac.authorization.k8s.io/argocd-server created
clusterrolebinding.rbac.authorization.k8s.io/argocd-application-controller created
clusterrolebinding.rbac.authorization.k8s.io/argocd-server created
configmap/argocd-cm created
configmap/argocd-gpg-keys-cm created
configmap/argocd-rbac-cm created
configmap/argocd-ssh-known-hosts-cm created
configmap/argocd-tls-certs-cm created
secret/argocd-secret created
service/argocd-dex-server created
service/argocd-metrics created
service/argocd-redis created
service/argocd-repo-server created
service/argocd-server-metrics created
service/argocd-server created
deployment.apps/argocd-application-controller created
deployment.apps/argocd-dex-server created
deployment.apps/argocd-redis created
deployment.apps/argocd-repo-server created
deployment.apps/argocd-server created


$ kubectl get all -n argocd
NAME                                                  READY   STATUS    RESTARTS   AGE
pod/argocd-application-controller-67bc594796-ntfjn    1/1     Running   0          83s
pod/argocd-dex-server-55675b569d-wctwt                1/1     Running   2          83s
pod/argocd-notifications-controller-64664db97-psnkx   1/1     Running   0          94m
pod/argocd-redis-54b6ff7bf6-2b4bm                     1/1     Running   0          83s
pod/argocd-repo-server-654d96b48-p5785                1/1     Running   0          83s
pod/argocd-server-bdcdd6f7c-hrvk4                     1/1     Running   0          83s

NAME                            TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                      AGE
service/argocd-dex-server       ClusterIP   10.100.58.24     <none>        5556/TCP,5557/TCP,5558/TCP   83s
service/argocd-metrics          ClusterIP   10.100.71.232    <none>        8082/TCP                     83s
service/argocd-redis            ClusterIP   10.100.37.118    <none>        6379/TCP                     83s
service/argocd-repo-server      ClusterIP   10.100.189.69    <none>        8081/TCP,8084/TCP            83s
service/argocd-server           ClusterIP   10.100.150.191   <none>        80/TCP,443/TCP               83s
service/argocd-server-metrics   ClusterIP   10.100.69.171    <none>        8083/TCP                     83s

NAME                                              READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/argocd-application-controller     1/1     1            1           83s
deployment.apps/argocd-dex-server                 1/1     1            1           83s
deployment.apps/argocd-notifications-controller   1/1     1            1           94m
deployment.apps/argocd-redis                      1/1     1            1           83s
deployment.apps/argocd-repo-server                1/1     1            1           83s
deployment.apps/argocd-server                     1/1     1            1           83s

NAME                                                        DESIRED   CURRENT   READY   AGE
replicaset.apps/argocd-application-controller-67bc594796    1         1         1       83s
replicaset.apps/argocd-dex-server-55675b569d                1         1         1       83s
replicaset.apps/argocd-notifications-controller-64664db97   1         1         1       94m
replicaset.apps/argocd-redis-54b6ff7bf6                     1         1         1       83s
replicaset.apps/argocd-repo-server-654d96b48                1         1         1       83s
replicaset.apps/argocd-server-bdcdd6f7c                     1         1         1       83s


# Argo CD Serverへアクセスするための設定
$ kubectl patch svc argocd-server -n argocd -p '{"spec": {"type": "LoadBalancer"}}'
service/argocd-server patched

$ kubectl get svc -n argocd
NAME                    TYPE           CLUSTER-IP       EXTERNAL-IP
               PORT(S)                      AGE
argocd-dex-server       ClusterIP      10.100.58.24     <none>
               5556/TCP,5557/TCP,5558/TCP   9m18s
argocd-metrics          ClusterIP      10.100.71.232    <none>
               8082/TCP                     9m18s
argocd-redis            ClusterIP      10.100.37.118    <none>
               6379/TCP                     9m18s
argocd-repo-server      ClusterIP      10.100.189.69    <none>
               8081/TCP,8084/TCP            9m18s
argocd-server           LoadBalancer   10.100.150.191   a6810607dbc8944c08df1e90108554ed-1651594083.ap-northeast-1.elb.amazonaws.com   80:30635/TCP,443:30162/TCP   9m18s
argocd-server-metrics   ClusterIP      10.100.69.171    <none>
               8083/TCP                     9m18s


# 初期ログインパスワードの確認
$ kubectl get pods -n argocd -l app.kubernetes.io/name=argocd-server -o name | cut -d'/' -f 2
argocd-server-bdcdd6f7c-hrvk4


# argocdコマンドによるログイン
$ argocd login a6810607dbc8944c08df1e90108554ed-1651594083.ap-northeast-1.elb.amazonaws.com:443
WARNING: server certificate had error: x509: certificate is valid for localhost, argocd-server, argocd-server.argocd, argocd-server.argocd.svc, argocd-server.argocd.svc.cluster.local, not a6810607dbc8944c08df1e90108554ed-1651594083.ap-northeast-1.elb.amazonaws.com. Proceed insecurely (y/n)? y
Username: admin
Password:
'admin' logged in successfully
Context 'a6810607dbc8944c08df1e90108554ed-1651594083.ap-northeast-1.elb.amazonaws.com:443' updated

今回はこちらのGitHubリポジトリに配置したマニフェストファイルを利用し、アプリケーションを起動します。 こちらは、以前Argo Rolloutsの挙動を見たときに使ったコンテナイメージとマニフェストファイルになります。

Dockerfile-fail

FROM nginx:latest

CMD head /foo/bar

deployment.yml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: testdeployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: test
  template:
    metadata:
      name: testpod
      labels:
        app: test
    spec:
      containers:
        - name: nginx-container
          image: nginx:latest
          ports:
          - containerPort: 80

testapp-fail.yml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: testapp-fail
spec:
  replicas: 1
  selector:
    matchLabels:
      app: fail
  template:
    metadata:
      name: testpod-fail
      labels:
        app: fail
    spec:
      containers:
        - name: nginx-container
          image: 111111111111.dkr.ecr.ap-northeast-1.amazonaws.com/test/test03:ok
          ports:
          - containerPort: 80

application.yml

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: testapp
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  project: default
  source:
    repoURL: https://github.com/FY0323/argocd-test.git
    targetRevision: HEAD
    path: testpod

  destination: 
    server: https://kubernetes.default.svc
    namespace: default

  syncPolicy: 
    automated:
      prune: false
      selfHeal: false
# アプリケーションのデプロイ
$ kubectl apply -f application.yml
application.argoproj.io/testapp created

# デプロイ後の確認
$ kubectl get app -n argocd
NAME      AGE
testapp   13s

$ kubectl get pods
NAME                               READY   STATUS    RESTARTS   AGE
testapp-fail-02-6689b9c479-lpcs9   1/1     Running   0          18s
testapp-fail-69795ddcc-vb9sn       1/1     Running   0          18s
testdeployment-f4796ffcb-kjbn5     1/1     Running   0          18s

Applicationがデプロイされたので、ここでargocd app syncコマンドやWeb UIからの操作により、GitHubリポジトリとの同期を行うことができますが、この状態ではまだSlackへの通知は実行されません。

Argo CD NotificationsによるSlackへの通知を行うには、Applicationリソースに対し、Slackへの通知と通知先となるチャンネルを指定したAnnotationを付与する必要があります。

# Annotationの付与
$ kubectl patch app testapp -n argocd -p '{"metadata": {"annotations": {"recipients.argocd-notifications.argoproj.io":"slack:test-argocd-notification"}}}' --type merge
application.argoproj.io/testapp patched

$ kubectl describe app testapp -n argocd | head
Name:         testapp
Namespace:    argocd
Labels:       <none>
Annotations:  recipients.argocd-notifications.argoproj.io: slack:test-argocd-notification
API Version:  argoproj.io/v1alpha1
Kind:         Application
Metadata:
  Creation Timestamp:  2020-10-21T08:23:06Z
  Finalizers:
    resources-finalizer.argocd.argoproj.io

Annotationを付与することで、モニタリング対象のApplicationが設定されたことになり、Argo CD Notificationsのモニタリングが有効となります。

この後、GitHubリポジトリとの同期を行うことで、指定したSlackチャンネルに対する通知が飛ばされます。

# Syncの例
# 実行時ログを失ってしまったため、日時にズレあり
$ argocd app sync testapp
TIMESTAMP                  GROUP        KIND   NAMESPACE                  NAME    STATUS   HEALTH        HOOK  MESSAGE
2020-10-25T17:11:47+09:00   apps  Deployment     default          testapp-fail    Synced  Healthy
2020-10-25T17:11:47+09:00   apps  Deployment     default       testapp-fail-02    Synced  Healthy
2020-10-25T17:11:47+09:00   apps  Deployment     default        testdeployment    Synced  Healthy
2020-10-25T17:11:48+09:00   apps  Deployment     default       testapp-fail-02    Synced  Healthy              deployment.apps/testapp-fail-02 unchanged
2020-10-25T17:11:48+09:00   apps  Deployment     default          testapp-fail    Synced  Healthy              deployment.apps/testapp-fail unchanged
2020-10-25T17:11:48+09:00   apps  Deployment     default        testdeployment    Synced  Healthy              deployment.apps/testdeployment unchanged

Name:               testapp
Project:            default
Server:             https://kubernetes.default.svc
Namespace:          default
URL:                https://a6810607dbc8944c08df1e90108554ed-1651594083.ap-northeast-1.elb.amazonaws.com/applications/testapp
Repo:               https://github.com/FY0323/argocd-test.git
Target:             HEAD
Path:               testpod
SyncWindow:         Sync Allowed
Sync Policy:        Automated
Sync Status:        Synced to HEAD (9f4d5d7)
Health Status:      Healthy

Operation:          Sync
Sync Revision:      9f4d5d734d44e3c4412ba5b6a2d0aab6353b8610
Phase:              Succeeded
Start:              2020-10-25 17:11:47 +0900 JST
Finished:           2020-10-25 17:11:48 +0900 JST
Duration:           1s
Message:            successfully synced (all tasks run)

GROUP  KIND        NAMESPACE  NAME             STATUS  HEALTH   HOOK  MESSAGE
apps   Deployment  default    testapp-fail-02  Synced  Healthy        deployment.apps/testapp-fail-02 unchanged
apps   Deployment  default    testapp-fail     Synced  Healthy        deployment.apps/testapp-fail unchanged
apps   Deployment  default    testdeployment   Synced  Healthy        deployment.apps/testdeployment unchanged

f:id:FY0323:20201028085909p:plain

以上のとおり、Slackへの通知が確認できました。

また、Argo CDはデフォルトで3分おきにリポジトリの状態を確認します。リポジトリ上に変更がない場合に通知を飛ばすか気になっていましたが、しばらく放置しても何の通知も飛ばさなかったため、特に通知をすることはなさそうです。リポジトリにあるマニフェスト情報を更新すれば、しばらくした後に通知が来ることも確認しています。

Argo CD Notificationsの設定を変更する

Argo CD NotificationsはTriggerとTemplateを自分で設定することも可能です。ビルトインで用意されているTrigger/Templateで不十分な場合は、カスタマイズすることができます。

Triggerを変更する

まずは、先ほどargocd-notification-cm.yamlConfigMapで指定したTriggerを変更します。ここではSyncに失敗しDegradedの状態となった場合に通知を飛ばすon-health-degradedを利用します。

以下のようにargocd-notification-cm.yamlを書き換えます。

argocd-notification-cm.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-notifications-cm
  namespace: argocd
data:
  config.yaml: |
    triggers:
      - name: on-health-degraded # 変更
        enabled: true
# ConfigMapの変更
$ vi argocd-notification-cm.yaml

$ kubectl apply -f argocd-notification-cm.yaml
configmap/argocd-notifications-cm configured

$ kubectl describe cm argocd-notifications-cm -n argocd
Name:         argocd-notifications-cm
Namespace:    argocd
Labels:       <none>
Annotations:
Data
====
config.yaml:
----
triggers:
  - name: on-health-degraded
    enabled: true

Events:  <none>

次にマニフェストファイルの一部を変更します。

# GitHubのWeb UI上で操作
# GitHub上で以下の箇所を修正しCommit

apiVersion: apps/v1
kind: Deployment
metadata:
  name: testapp-fail
spec:
  replicas: 1
  selector:
    matchLabels:
      app: fail
  template:
    metadata:
      name: testpod-fail
      labels:
        app: fail
    spec:
      containers:
        - name: nginx-container
          image: 111111111111.dkr.ecr.ap-northeast-1.amazonaws.com/test/test03:fail # イメージタグの変更
          ports:
          - containerPort: 80

上記箇所を変更し、argocd app syncコマンドを実行します。

$ argocd app sync testapp
TIMESTAMP                  GROUP        KIND   NAMESPACE                  NAME    STATUS   HEALTH        HOOK  MESSAGE
2020-10-21T22:53:53+09:00   apps  Deployment     default          testapp-fail    Synced  Healthy
2020-10-21T22:53:53+09:00   apps  Deployment     default       testapp-fail-02    Synced  Healthy
2020-10-21T22:53:53+09:00   apps  Deployment     default        testdeployment    Synced  Healthy
2020-10-21T22:53:54+09:00   apps  Deployment     default       testapp-fail-02    Synced  Healthy              deployment.apps/testapp-fail-02 unchanged
2020-10-21T22:53:55+09:00   apps  Deployment     default        testdeployment    Synced  Healthy              deployment.apps/testdeployment unchanged
2020-10-21T22:53:55+09:00   apps  Deployment     default          testapp-fail    Synced  Healthy              deployment.apps/testapp-fail configured
2020-10-21T22:53:55+09:00   apps  Deployment     default          testapp-fail  OutOfSync  Progressing              deployment.apps/testapp-fail configured

Name:               testapp
Project:            default
Server:             https://kubernetes.default.svc
Namespace:          default
URL:                https://a6810607dbc8944c08df1e90108554ed-1651594083.ap-northeast-1.elb.amazonaws.com/applications/testapp
Repo:               https://github.com/FY0323/argocd-test.git
Target:             HEAD
Path:               testpod
SyncWindow:         Sync Allowed
Sync Policy:        Automated
Sync Status:        OutOfSync from HEAD (a6c2f93)
Health Status:      Progressing

Operation:          Sync
Sync Revision:      06215a95898d03b264481cc1c592016f8f1a43d8
Phase:              Succeeded
Start:              2020-10-21 22:53:56 +0900 JST
Finished:           2020-10-21 22:53:57 +0900 JST
Duration:           1s
Message:            successfully synced (all tasks run)

GROUP  KIND        NAMESPACE  NAME             STATUS     HEALTH       HOOK  MESSAGE
apps   Deployment  default    testapp-fail-02  Synced     Healthy            deployment.apps/testapp-fail-02 unchanged
apps   Deployment  default    testdeployment   Synced     Healthy            deployment.apps/testdeployment unchanged
apps   Deployment  default    testapp-fail     OutOfSync  Progressing        deployment.apps/testapp-fail configured

しばらくするとDegradedの状態となり、Slackに通知が飛んできます。

$ argocd app get testapp
Name:               testapp
Project:            default
Server:             https://kubernetes.default.svc
Namespace:          default
URL:                https://a6810607dbc8944c08df1e90108554ed-1651594083.ap-northeast-1.elb.amazonaws.com/applications/testapp
Repo:               https://github.com/FY0323/argocd-test.git
Target:             HEAD
Path:               testpod
SyncWindow:         Sync Allowed
Sync Policy:        Automated
Sync Status:        Synced to HEAD (a6c2f93)
Health Status:      Degraded

GROUP  KIND        NAMESPACE  NAME             STATUS  HEALTH    HOOK  MESSAGE
apps   Deployment  default    testapp-fail-02  Synced  Healthy         deployment.apps/testapp-fail-02 unchanged
apps   Deployment  default    testdeployment   Synced  Healthy         deployment.apps/testdeployment unchanged
apps   Deployment  default    testapp-fail     Synced  Degraded        deployment.apps/testapp-fail configured

f:id:FY0323:20201028085050p:plain

Templateを変更する

次にTemplateのほうを変更してみます。Argo CD NotificationsはGoのhtml/templateをベースにtemplateを利用しており、titleとbodyを設定することが可能です。またapp fieldでApplicationの情報を利用することや、time repoといった関数を利用することもできます

今回は以下のようなTemplateを用意しました。

argocd-notification-cm.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-notifications-cm
  namespace: argocd
data:
  config.yaml: |
    triggers:
      - name: test-trigger
        template: test-template
        condition: time.Now().Sub(time.Parse(app.status.operationState.startedAt)).Minutes() <= 5
        enabled: true
    templates:
      - name: test-template
        body: |
          "Application: {{.app.metadata.name}} sync status is {{.app.status.sync.status}}"
          "Repository: {{.app.spec.source.repoURL}}" 
          "Images: {{.app.status.summary.images}}"
          "Revision: {{.app.status.sync.revision}}"

上記ファイルについて補足します。

  • triggers: 以下の4つの項目を設定することができます。
    • name: Triggerの名前
    • template: 利用するTemplateを指定
    • condition: 通知を飛ばす条件を指定します。
      • time … <= 5: app.status.operationState.startedAt、つまり同期開始から5分以内に同期が完了した場合に通知します
    • enabled: Triggerの有効・無効を選択します
  • templates: 以下の2つの項目を設定することができます。
    • name: Templateの名前
    • title: 通知時のタイトルを指定します(今回は使用せず)
    • body: 通知時の内容を指定します。今回はApplicationの中で必要そうな情報を並べただけになります

上記マニフェストファイルを利用し、ConfigMapを更新します。

$ kubectl apply -f argocd-notification-cm.yaml
configmap/argocd-notifications-cm configured

$ kubectl describe cm argocd-notifications-cm -n argocd
Name:         argocd-notifications-cm
Namespace:    argocd
Labels:       <none>
Annotations:
Data
====
config.yaml:
----
triggers:
  - name: test-trigger
    template: test-template
    condition: time.Now().Sub(time.Parse(app.status.operationState.startedAt)).Minutes() <= 5
    enabled: true
templates:
  - name: test-template
    body: |
      "Application: {{.app.metadata.name}} sync status is {{.app.status.sync.status}}"
      "Repository: {{.app.spec.source.repoURL}}"
      "Images: {{.app.status.summary.images}}"
      "Revision: {{.app.status.sync.revision}}"

Events:  <none>

上記ファイルをデプロイ後、argocd app syncコマンドを実行すると、以下のようなメッセージが確認できます。

f:id:FY0323:20201028131444p:plain

参考ドキュメント

Argo RolloutsのAnalysisTemplateを用いた自動ロールバックを試してみる

Argo Rolloutsは、Kubernetesで利用できるRolling Updateよりも高度なデプロイ・リリース方式を利用することができます。その中には「Progressive Delivery」という、デプロイ後に特定の分析を行い、デプロイの結果を評価するという方式も含まれています。

Argo RolloutsにはAnalysisTemplate AnalysisRunなど、分析に関するCRDが含まれており、この結果をもとに自動ロールバックを実行することができます。

今回はArgo Rolloutsで利用できる自動ロールバックを試してみました。Argo Rolloutsの概要については、前回の記事をご覧ください。

検証環境

今回の環境は以下の通りです。

Argo Rolloutsのデプロイ

まずはArgo Rolloutsを利用できるよう、Kubernetes環境にデプロイします。

# Argo Rolloutsのデプロイ
$ git clone https://github.com/argoproj/argo-rollouts.git
$ cd argo-rollouts/manifests/
$ kubectl create ns argo-rollouts
namespace/argo-rollouts created

$ kubectl get ns
NAME              STATUS   AGE
argo-rollouts     Active   5s
default           Active   63m
kube-node-lease   Active   63m
kube-public       Active   63m
kube-system       Active   63m

$ kubectl apply -n argo-rollouts -f install.yaml
customresourcedefinition.apiextensions.k8s.io/analysisruns.argoproj.io created
customresourcedefinition.apiextensions.k8s.io/analysistemplates.argoproj.io created
customresourcedefinition.apiextensions.k8s.io/clusteranalysistemplates.argoproj.io created
customresourcedefinition.apiextensions.k8s.io/experiments.argoproj.io created
customresourcedefinition.apiextensions.k8s.io/rollouts.argoproj.io created
serviceaccount/argo-rollouts created
role.rbac.authorization.k8s.io/argo-rollouts-role created
clusterrole.rbac.authorization.k8s.io/argo-rollouts-aggregate-to-admin created
clusterrole.rbac.authorization.k8s.io/argo-rollouts-aggregate-to-edit created
clusterrole.rbac.authorization.k8s.io/argo-rollouts-aggregate-to-view created
clusterrole.rbac.authorization.k8s.io/argo-rollouts-clusterrole created
rolebinding.rbac.authorization.k8s.io/argo-rollouts-role-binding created
clusterrolebinding.rbac.authorization.k8s.io/argo-rollouts-clusterrolebinding created
service/argo-rollouts-metrics created
deployment.apps/argo-rollouts created

# デプロイ後の確認
$ kubectl get all -n argo-rollouts
NAME                                 READY   STATUS    RESTARTS   AGE
pod/argo-rollouts-8454b64759-rhf47   1/1     Running   0          9s

NAME                            TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
service/argo-rollouts-metrics   ClusterIP   10.100.217.123   <none>        8090/TCP   10s

NAME                            READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/argo-rollouts   1/1     1            1           9s

NAME                                       DESIRED   CURRENT   READY   AGE
replicaset.apps/argo-rollouts-8454b64759   1         1         1       9s

AnalysisTemplateを利用しない場合

ここから、実際にArgo Rolloutsを利用します。今回はBlue/Green Deploymentを利用したときの様子を見ていきます。またAnalysisTemplateを利用しない場合と、利用した場合を試し、AnalysisTemplateを利用することでどう変わるかを見ていきます。

まずはAnalysisTemplateを利用しない場合を見てみます。

Rolloutのデプロイ

今回は以下のようなRollout Service用のファイルを利用しました。Argo RolloutsでBlue/Green Deploymentを利用する場合、activeServiceというServiceを指定する必要があります。指定したServiceが存在しない場合、Rolloutが作成された後もPodが作成されません。

rollout-bg-test.yml

apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: rollout-bg-test
spec:
  replicas: 2
  revisionHistoryLimit: 2
  selector:
    matchLabels:
      app: rollout-bg
  template:
    metadata:
      labels:
        app: rollout-bg
    spec:
      containers:
        - name: nginx-container
          image: nginx:latest
          ports:
          - containerPort: 80
  strategy:
    blueGreen:
      autoPromotionEnabled: true
      activeService: rollout-active-service

rollout-service.yml

apiVersion: v1
kind: Service
metadata:
  name: rollout-active-service
spec:
  ports:
  - port: 8080
    targetPort: 80
    protocol: TCP
  selector:
    app: rollout-bg

上記2つのリソースをデプロイします。

# Rolloutの作成
$ kubectl apply -f rollout-service.yml
service/rollout-active-service created

$ kubectl apply -f rollout-bg-test.yml
rollout.argoproj.io/rollout-bg-test created


# デプロイ後の確認
$ kubectl get svc
NAME                     TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
kubernetes               ClusterIP   10.100.0.1      <none>        443/TCP    21h
rollout-active-service   ClusterIP   10.100.121.64   <none>        8080/TCP   33s

$ kubectl get pods
NAME                               READY   STATUS    RESTARTS   AGE
rollout-bg-test-797d88cdd8-4ww9s   1/1     Running   0          14s
rollout-bg-test-797d88cdd8-stc74   1/1     Running   0          14s

$ kubectl get rollout
NAME              DESIRED   CURRENT   UP-TO-DATE   AVAILABLE
rollout-bg-test   2         2         2            2

$ kubectl argo rollouts get rollout rollout-bg-test
Name:            rollout-bg-test
Namespace:       default
Status:          ✔ Healthy
Strategy:        BlueGreen
Images:          nginx:latest (active)
Replicas:
  Desired:       2
  Current:       2
  Updated:       2
  Ready:         2
  Available:     2

NAME                                         KIND        STATUS     AGE  INFO
⟳ rollout-bg-test                            Rollout     ✔ Healthy  62s
└──# revision:1
   └──⧉ rollout-bg-test-797d88cdd8           ReplicaSet  ✔ Healthy  62s  active
      ├──□ rollout-bg-test-797d88cdd8-4ww9s  Pod         ✔ Running  62s  ready:1/1
      └──□ rollout-bg-test-797d88cdd8-stc74  Pod         ✔ Running  62s  ready:1/1

Rolloutのアップデート

次に、デプロイしたRolloutのイメージタグを変更し、アップデートされる様子を見ていきます。

# 別のウィンドウを開き、事前に実行する
$ kubectl argo rollouts get rollout rollout-bg-test -w

# イメージタグの変更
$ kubectl argo rollouts set image rollout-bg-test nginx-container=nginx:stable
rollout "rollout-bg-test" image updated


# アップデートの経過を確認する
## イメージタグ変更直後
Name:            rollout-bg-test
Namespace:       default
Status:          ✔ Healthy
Strategy:        BlueGreen
Images:          nginx:latest (active)
Replicas:
  Desired:       2
  Current:       2
  Updated:       2
  Ready:         2
  Available:     2

NAME                                         KIND        STATUS     AGE    INFO
⟳ rollout-bg-test                            Rollout     ✔ Healthy  2m49s
└──# revision:1
   └──⧉ rollout-bg-test-797d88cdd8           ReplicaSet  ✔ Healthy  2m49s  active
      ├──□ rollout-bg-test-797d88cdd8-4ww9s  Pod         ✔ Running  2m49s  ready:1/1
      └──□ rollout-bg-test-797d88cdd8-stc74  Pod         ✔ Running  2m49s  ready:1/1


## アップデート開始
Name:            rollout-bg-test
Namespace:       default
Status:          ✔ Healthy
Strategy:        BlueGreen
Images:          nginx:latest (active)
Replicas:
  Desired:       2
  Current:       2
  Updated:       2
  Ready:         2
  Available:     2

NAME                                         KIND        STATUS         AGE    INFO
⟳ rollout-bg-test                            Rollout     ✔ Healthy      2m50s
├──# revision:2
│  └──⧉ rollout-bg-test-5fd48d44d            ReplicaSet  ◌ Progressing  0s
└──# revision:1
   └──⧉ rollout-bg-test-797d88cdd8           ReplicaSet  ✔ Healthy      2m50s  active
      ├──□ rollout-bg-test-797d88cdd8-4ww9s  Pod         ✔ Running      2m50s  ready:1/1
      └──□ rollout-bg-test-797d88cdd8-stc74  Pod         ✔ Running      2m50s  ready:1/1


Name:            rollout-bg-test
Namespace:       default
Status:          ◌ Progressing
Strategy:        BlueGreen
Images:          nginx:latest (active)
                 nginx:stable
Replicas:
  Desired:       2
  Current:       4
  Updated:       2
  Ready:         2
  Available:     0

NAME                                         KIND        STATUS               AGE    INFO
⟳ rollout-bg-test                            Rollout     ◌ Progressing        2m50s
├──# revision:2
│  └──⧉ rollout-bg-test-5fd48d44d            ReplicaSet  ◌ Progressing        0s
│     ├──□ rollout-bg-test-5fd48d44d-89nnw   Pod         ◌ ContainerCreating  0s     ready:0/1
│     └──□ rollout-bg-test-5fd48d44d-s8ntr   Pod         ◌ ContainerCreating  0s     ready:0/1
└──# revision:1
   └──⧉ rollout-bg-test-797d88cdd8           ReplicaSet  ✔ Healthy            2m50s  active
      ├──□ rollout-bg-test-797d88cdd8-4ww9s  Pod         ✔ Running            2m50s  ready:1/1
      └──□ rollout-bg-test-797d88cdd8-stc74  Pod         ✔ Running            2m50s  ready:1/1


## 切り替え完了
## revision 2のReplicaSetがactiveとなり、revision 1は削除されるまでの時刻がカウントダウンされる 
Name:            rollout-bg-test
Namespace:       default
Status:          ◌ Progressing
Strategy:        BlueGreen
Images:          nginx:latest
                 nginx:stable (active)
Replicas:
  Desired:       2
  Current:       4
  Updated:       2
  Ready:         4
  Available:     2

NAME                                         KIND        STATUS         AGE    INFO
⟳ rollout-bg-test                            Rollout     ◌ Progressing  2m52s
├──# revision:2
│  └──⧉ rollout-bg-test-5fd48d44d            ReplicaSet  ✔ Healthy      1s     active
│     ├──□ rollout-bg-test-5fd48d44d-89nnw   Pod         ✔ Running      1s     ready:1/1
│     └──□ rollout-bg-test-5fd48d44d-s8ntr   Pod         ✔ Running      1s     ready:1/1
└──# revision:1
   └──⧉ rollout-bg-test-797d88cdd8           ReplicaSet  ✔ Healthy      2m52s  delay:30s
      ├──□ rollout-bg-test-797d88cdd8-4ww9s  Pod         ✔ Running      2m52s  ready:1/1
      └──□ rollout-bg-test-797d88cdd8-stc74  Pod         ✔ Running      2m52s  ready:1/1


## 完了後
Name:            rollout-bg-test
Namespace:       default
Status:          ✔ Healthy
Strategy:        BlueGreen
Images:          nginx:stable (active)
Replicas:
  Desired:       2
  Current:       2
  Updated:       2
  Ready:         2
  Available:     2

NAME                                        KIND        STATUS        AGE    INFO
⟳ rollout-bg-test                           Rollout     ✔ Healthy     6m7s
├──# revision:2
│  └──⧉ rollout-bg-test-5fd48d44d           ReplicaSet  ✔ Healthy     3m16s  active
│     ├──□ rollout-bg-test-5fd48d44d-89nnw  Pod         ✔ Running     3m16s  ready:1/1
│     └──□ rollout-bg-test-5fd48d44d-s8ntr  Pod         ✔ Running     3m16s  ready:1/1
└──# revision:1
   └──⧉ rollout-bg-test-797d88cdd8          ReplicaSet  • ScaledDown  6m7s

完了後のリソースは以下の通りです。

$ kubectl get rollout
NAME              DESIRED   CURRENT   UP-TO-DATE   AVAILABLE
rollout-bg-test   2         2         2            2

$ kubectl describe rollout
Name:         rollout-bg-test
Namespace:    default
Labels:       <none>
Annotations:  rollout.argoproj.io/revision: 2
API Version:  argoproj.io/v1alpha1
Kind:         Rollout
Metadata:
  Creation Timestamp:  2020-10-10T01:57:48Z
  Generation:          14
  Resource Version:    237409
  Self Link:           /apis/argoproj.io/v1alpha1/namespaces/default/rollouts/rollout-bg-test
  UID:                 d5ba96ef-2292-4f3c-91f1-cd0427c50f59
Spec:
  Replicas:                2
  Revision History Limit:  2
  Selector:
    Match Labels:
      App:  rollout-bg
  Strategy:
    Blue Green:
      Active Service:          rollout-active-service
      Auto Promotion Enabled:  true
  Template:
    Metadata:
      Creation Timestamp:  <nil>
      Labels:
        App:  rollout-bg
    Spec:
      Containers:
        Image:  nginx:stable
        Name:   nginx-container
        Ports:
          Container Port:  80
        Resources:
Status:
  HPA Replicas:        2
  Available Replicas:  2
  Blue Green:
    Active Selector:  5fd48d44d
  Canary:
  Conditions:
    Last Transition Time:  2020-10-10T01:57:52Z
    Last Update Time:      2020-10-10T01:57:52Z
    Message:               Rollout has minimum availability
    Reason:                AvailableReason
    Status:                True
    Type:                  Available
    Last Transition Time:  2020-10-10T01:57:48Z
    Last Update Time:      2020-10-10T02:00:41Z
    Message:               ReplicaSet "rollout-bg-test-5fd48d44d" has successfully progressed.
    Reason:                NewReplicaSetAvailable
    Status:                True
    Type:                  Progressing
  Current Pod Hash:        5fd48d44d
  Observed Generation:     576f58fbb8
  Ready Replicas:          2
  Replicas:                2
  Selector:                app=rollout-bg,rollouts-pod-template-hash=5fd48d44d
  Stable RS:               5fd48d44d
  Updated Replicas:        2
Events:
  Type    Reason             Age    From                 Message
  ----    ------             ----   ----                 -------
  Normal  ScalingReplicaSet  7m52s  rollouts-controller  Scaled up replica set rollout-bg-test-797d88cdd8 to 2
  Normal  SwitchService      7m48s  rollouts-controller  Switched selector for service 'rollout-active-service' to value '797d88cdd8'
  Normal  ScalingReplicaSet  5m1s   rollouts-controller  Scaled up replica set rollout-bg-test-5fd48d44d to 2
  Normal  SwitchService      4m59s  rollouts-controller  Switched selector for service 'rollout-active-service' to value '5fd48d44d'
  Normal  ScalingReplicaSet  4m29s  rollouts-controller  Scaled down replica set rollout-bg-test-797d88cdd8 to 0

AnalysisTemplateを利用する場合

ここからはRollout Serviceに加えAnalysisTemplateリソースを作成し、ロールアウト実行時にAnalysisが実行されるようにします。

今回は、Analysisに成功した場合・失敗した場合を見るために、実行後exit 0を返す(=Analysisに必ず成功する)ようなAnalysisTemplateを用意します。そして、デプロイ後にkubectl editコマンドによってAnalysisTemplateを編集し、実行後exit 1を返す(=Analysisに必ず失敗する)ようにして失敗した場合を見てみます。

AnalysisTemplateは、Rolloutリソース中で宣言をされると、Rolloutのアップデート時に実行されます。実行と書きましたが、実際はAnalysisRunという、分析を実行するためのCRDが作成され、AnalysisTemplateに定義された内容を元に分析を実行します。

AnalysisTemplateで実行する分析では様々な種類のメトリクスを利用することができます。今回はKubernetesリソースの1つであるJobを利用しました。AnalysisRunによって分析が実行されると、分析を行うためのJobが作成され、Jobが正常に終了すれば成功となります。

Argo RolloutsのBlue/Green Deploymentは、Analysisを実行するタイミングとしてprePromotionAnalysis postPromotionAnalysisのどちらかを利用することができ、それぞれ切り替え前・切り替え後に実行をすることができます。今回はpostPromotionAnalysisを利用し、切り替え後にAnalysisを実行して、失敗した場合は元のバージョンへの切り戻しを行い、古いバージョンへロールバックするようにします。

Rolloutのデプロイ

ここでは以下の3つのファイルを利用します。

rollout-bg-test-analysis.yml

apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: rollout-bg-test-analysis
spec:
  replicas: 2
  revisionHistoryLimit: 2
  selector:
    matchLabels:
      app: rollout-bg-analysis
  template:
    metadata:
      labels:
        app: rollout-bg-analysis
    spec:
      containers:
        - name: nginx-container
          image: nginx:latest
          ports:
          - containerPort: 80
  strategy:
    blueGreen:
      activeService: rollout-active-service-analysis
      postPromotionAnalysis:
        templates:
        - templateName: test-analysis

rollout-active-service-analysis.yml

apiVersion: v1
kind: Service
metadata:
  name: rollout-active-service-analysis
spec:
  ports:
  - port: 8080
    targetPort: 80
    protocol: TCP
  selector:
    app: rollout-bg-analysis

test-analysistemp.yml

kind: AnalysisTemplate
apiVersion: argoproj.io/v1alpha1
metadata:
  name: test-analysis
spec:
  metrics:
  - name: test-analysis
    provider:
      job:
        spec:
          template:
            spec:
              containers:
              - name: sleep
                image: alpine:3.8
                command: [sh, -c]
                args: [exit 0]
              restartPolicy: Never
          backoffLimit: 1

上記3つのファイルをデプロイします。

# デプロイ
$ kubectl apply -f test-analysistemp.yml
analysistemplate.argoproj.io/test-analysis created

$ kubectl apply -f rollout-active-service-analysis.yml
service/rollout-active-service-analysis created

$ kubectl apply -f rollout-bg-test-analysis.yml
rollout.argoproj.io/rollout-bg-test-analysis created


# デプロイ後の確認
$ kubectl get analysistemplate
NAME            AGE
test-analysis   23s

$ kubectl get svc
NAME                              TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
kubernetes                        ClusterIP   10.100.0.1      <none>        443/TCP    21h
rollout-active-service-analysis   ClusterIP   10.100.255.31   <none>        8080/TCP   20s

$ kubectl get pods
NAME                                        READY   STATUS    RESTARTS   AGE
rollout-bg-test-analysis-766b7567dc-qgpzx   1/1     Running   0          15s
rollout-bg-test-analysis-766b7567dc-tzlft   1/1     Running   0          15s

$ kubectl get rollout
NAME                       DESIRED   CURRENT   UP-TO-DATE   AVAILABLE
rollout-bg-test-analysis   2         2         2            2

$ kubectl argo rollouts get rollout rollout-bg-test-analysis
Name:            rollout-bg-test-analysis
Namespace:       default
Status:          ✔ Healthy
Strategy:        BlueGreen
Images:          nginx:latest (active)
Replicas:
  Desired:       2
  Current:       2
  Updated:       2
  Ready:         2
  Available:     2

NAME                                                  KIND        STATUS     AGE  INFO
⟳ rollout-bg-test-analysis                            Rollout     ✔ Healthy  26s
└──# revision:1
   └──⧉ rollout-bg-test-analysis-766b7567dc           ReplicaSet  ✔ Healthy  26s  active
      ├──□ rollout-bg-test-analysis-766b7567dc-qgpzx  Pod         ✔ Running  26s  ready:1/1
      └──□ rollout-bg-test-analysis-766b7567dc-tzlft  Pod         ✔ Running  26s  ready:1/1

Rolloutのアップデート (Analysisに成功した場合)

まずはAnalysisに成功した場合を見てみます。先ほどと同様にイメージタグを更新してみます。

# 事前に別ターミナルで実行
$ kubectl argo rollouts get rollout rollout-bg-test-analysis --watch


# イメージタグの更新
$ kubectl argo rollouts set image rollout-bg-test-analysis nginx-container=nginx:stable
rollout "rollout-bg-test-analysis" image updated


# アップデートの経過を確認する
## 開始直後

Name:            rollout-bg-test-analysis
Namespace:       default
Status:          ✔ Healthy
Strategy:        BlueGreen
Images:          nginx:latest (active)
Replicas:
  Desired:       2
  Current:       2
  Updated:       2
  Ready:         2
  Available:     2

NAME                                                  KIND        STATUS         AGE   INFO
⟳ rollout-bg-test-analysis                            Rollout     ✔ Healthy      2m9s
├──# revision:2
│  └──⧉ rollout-bg-test-analysis-6bcfbc585f           ReplicaSet  ◌ Progressing  0s
└──# revision:1
   └──⧉ rollout-bg-test-analysis-766b7567dc           ReplicaSet  ✔ Healthy      2m9s  active
      ├──□ rollout-bg-test-analysis-766b7567dc-qgpzx  Pod         ✔ Running      2m9s  ready:1/1
      └──□ rollout-bg-test-analysis-766b7567dc-tzlft  Pod         ✔ Running      2m9s  ready:1/1


## 新規Podの作成
Name:            rollout-bg-test-analysis
Namespace:       default
Status:          ◌ Progressing
Strategy:        BlueGreen
Images:          nginx:latest (active)
                 nginx:stable
Replicas:
  Desired:       2
  Current:       4
  Updated:       2
  Ready:         3
  Available:     1

NAME                                                  KIND        STATUS         AGE    INFO
⟳ rollout-bg-test-analysis                            Rollout     ◌ Progressing  2m10s
├──# revision:2
│  └──⧉ rollout-bg-test-analysis-6bcfbc585f           ReplicaSet  ◌ Progressing  0s
│     ├──□ rollout-bg-test-analysis-6bcfbc585f-cq2q5  Pod         ✔ Running      0s     ready:1/1
│     └──□ rollout-bg-test-analysis-6bcfbc585f-dtxdc  Pod         ✔ Running      0s     ready:1/1
└──# revision:1
   └──⧉ rollout-bg-test-analysis-766b7567dc           ReplicaSet  ✔ Healthy      2m10s  active
      ├──□ rollout-bg-test-analysis-766b7567dc-qgpzx  Pod         ✔ Running      2m10s  ready:1/1
      └──□ rollout-bg-test-analysis-766b7567dc-tzlft  Pod         ✔ Running      2m10s  ready:1/1


## トラフィックの切り替え完了とAnalysisの開始
Name:            rollout-bg-test-analysis
Namespace:       default
Status:          ◌ Progressing
Strategy:        BlueGreen
Images:          nginx:latest
                 nginx:stable (active)
Replicas:
  Desired:       2
  Current:       4
  Updated:       2
  Ready:         4
  Available:     2

NAME                                                             KIND         STATUS         AGE    INFO
⟳ rollout-bg-test-analysis                                       Rollout      ◌ Progressing  2m10s
├──# revision:2
│  ├──⧉ rollout-bg-test-analysis-6bcfbc585f                      ReplicaSet   ✔ Healthy      0s     active
│  │  ├──□ rollout-bg-test-analysis-6bcfbc585f-cq2q5             Pod          ✔ Running      0s     ready:1/1
│  │  └──□ rollout-bg-test-analysis-6bcfbc585f-dtxdc             Pod          ✔ Running      0s     ready:1/1
│  └──α rollout-bg-test-analysis-6bcfbc585f-2                    AnalysisRun  ◌ Running      0s
│     └──⊞ 471f5e5b-553b-4f94-bae3-cefca88afcd6.test-analysis.1  Job          ◌ Running      0s
└──# revision:1
   └──⧉ rollout-bg-test-analysis-766b7567dc                      ReplicaSet   ✔ Healthy      2m10s  delay:30s
      ├──□ rollout-bg-test-analysis-766b7567dc-qgpzx             Pod          ✔ Running      2m10s  ready:1/1
      └──□ rollout-bg-test-analysis-766b7567dc-tzlft             Pod          ✔ Running      2m10s  ready:1/1


## Analysisの完了(成功)
## AnalysisRunの結果がSuccessfulとなっていることが確認できる
Name:            rollout-bg-test-analysis
Namespace:       default
Status:          ◌ Progressing
Strategy:        BlueGreen
Images:          nginx:latest
                 nginx:stable (active)
Replicas:
  Desired:       2
  Current:       4
  Updated:       2
  Ready:         4
  Available:     2

NAME                                                             KIND         STATUS         AGE    INFO
⟳ rollout-bg-test-analysis                                       Rollout      ◌ Progressing  2m11s
├──# revision:2
│  ├──⧉ rollout-bg-test-analysis-6bcfbc585f                      ReplicaSet   ✔ Healthy      1s     active
│  │  ├──□ rollout-bg-test-analysis-6bcfbc585f-cq2q5             Pod          ✔ Running      1s     ready:1/1
│  │  └──□ rollout-bg-test-analysis-6bcfbc585f-dtxdc             Pod          ✔ Running      1s     ready:1/1
│  └──α rollout-bg-test-analysis-6bcfbc585f-2                    AnalysisRun  ✔ Successful   0s     ✔ 1
│     └──⊞ 471f5e5b-553b-4f94-bae3-cefca88afcd6.test-analysis.1  Job          ✔ Successful   0s
└──# revision:1
   └──⧉ rollout-bg-test-analysis-766b7567dc                      ReplicaSet   ✔ Healthy      2m11s  delay:29s
      ├──□ rollout-bg-test-analysis-766b7567dc-qgpzx             Pod          ✔ Running      2m11s  ready:1/1
      └──□ rollout-bg-test-analysis-766b7567dc-tzlft             Pod          ✔ Running      2m11s  ready:1/1


## 完了後
Name:            rollout-bg-test-analysis
Namespace:       default
Status:          ✔ Healthy
Strategy:        BlueGreen
Images:          nginx:stable (active)
Replicas:
  Desired:       2
  Current:       2
  Updated:       2
  Ready:         2
  Available:     2

NAME                                                             KIND         STATUS        AGE    INFO
⟳ rollout-bg-test-analysis                                       Rollout      ✔ Healthy     2m45s
├──# revision:2
│  ├──⧉ rollout-bg-test-analysis-6bcfbc585f                      ReplicaSet   ✔ Healthy     35s    active
│  │  ├──□ rollout-bg-test-analysis-6bcfbc585f-cq2q5             Pod          ✔ Running     35s    ready:1/1
│  │  └──□ rollout-bg-test-analysis-6bcfbc585f-dtxdc             Pod          ✔ Running     35s    ready:1/1
│  └──α rollout-bg-test-analysis-6bcfbc585f-2                    AnalysisRun  ✔ Successful  34s    ✔ 1
│     └──⊞ 471f5e5b-553b-4f94-bae3-cefca88afcd6.test-analysis.1  Job          ✔ Successful  34s
└──# revision:1
   └──⧉ rollout-bg-test-analysis-766b7567dc                      ReplicaSet   • ScaledDown  2m45s

新しいバージョンのデプロイ後、ロールアウトが完了し、通信が切り替わった後にAnalysisを実行する様子、そしてAnalysisに成功した場合、そのまま新しいバージョンのほうに切り替わったまま、古いバージョンが削除される様子が確認できました。

なお、完了後のリソースは以下の通りです。ロールアウトが実行されることでAnalysisRunというリソースが作成・実行され、その結果を確認することができます。

$ kubectl get pods
NAME                                                         READY   STATUS      RESTARTS   AGE
471f5e5b-553b-4f94-bae3-cefca88afcd6.test-analysis.1-8hz2s   0/1     Completed   0          4m26s
rollout-bg-test-analysis-6bcfbc585f-cq2q5                    1/1     Running     0          4m27s
rollout-bg-test-analysis-6bcfbc585f-dtxdc                    1/1     Running     0          4m27s

$ kubectl get analysisrun
NAME                                    STATUS
rollout-bg-test-analysis-6bcfbc585f-2   Successful

$ kubectl get rollout
NAME                       DESIRED   CURRENT   UP-TO-DATE   AVAILABLE
rollout-bg-test-analysis   2         2         2            2

$ kubectl describe analysisrun rollout-bg-test-analysis-6bcfbc585f-2
Name:         rollout-bg-test-analysis-6bcfbc585f-2
Namespace:    default
Labels:       rollout-type=PostPromotion
              rollouts-pod-template-hash=6bcfbc585f
Annotations:  rollout.argoproj.io/revision: 2
API Version:  argoproj.io/v1alpha1
Kind:         AnalysisRun
Metadata:
  Creation Timestamp:  2020-10-10T02:27:13Z
  Generation:          3
  Owner References:
    API Version:           argoproj.io/v1alpha1
    Block Owner Deletion:  true
    Controller:            true
    Kind:                  Rollout
    Name:                  rollout-bg-test-analysis
    UID:                   e9ba7057-29db-491a-8618-13180c406fef
  Resource Version:        242399
  Self Link:               /apis/argoproj.io/v1alpha1/namespaces/default/analysisruns/rollout-bg-test-analysis-6bcfbc585f-2
  UID:                     471f5e5b-553b-4f94-bae3-cefca88afcd6
Spec:
  Metrics:
    Name:  test-analysis
    Provider:
      Job:
        Metadata:
          Creation Timestamp:  <nil>
        Spec:
          Backoff Limit:  1
          Template:
            Metadata:
              Creation Timestamp:  <nil>
            Spec:
              Containers:
                Args:
                  exit 0
                Command:
                  sh
                  -c
                Image:  alpine:3.8
                Name:   sleep
                Resources:
              Restart Policy:  Never
Status:
  Metric Results:
    Count:  1
    Measurements:
      Finished At:  2020-10-10T02:27:14Z
      Metadata:
        Job - Name:  471f5e5b-553b-4f94-bae3-cefca88afcd6.test-analysis.1
      Phase:         Successful
      Started At:    2020-10-10T02:27:13Z
    Name:            test-analysis
    Phase:           Successful
    Successful:      1
  Phase:             Successful
  Started At:        2020-10-10T02:27:13Z
Events:
  Type    Reason    Age   From                 Message
  ----    ------    ----  ----                 -------
  Normal  Complete  20m   rollouts-controller  metric 'test-analysis' completed Successful
  Normal  Complete  20m   rollouts-controller  analysis completed Successful


$ kubectl describe rollout rollout-bg-test-analysis
Name:         rollout-bg-test-analysis
Namespace:    default
Labels:       <none>
Annotations:  rollout.argoproj.io/revision: 2
API Version:  argoproj.io/v1alpha1
Kind:         Rollout
Metadata:
  Creation Timestamp:  2020-10-10T02:25:02Z
  Generation:          16
  Resource Version:    242503
  Self Link:           /apis/argoproj.io/v1alpha1/namespaces/default/rollouts/rollout-bg-test-analysis
  UID:                 e9ba7057-29db-491a-8618-13180c406fef
Spec:
  Replicas:                2
  Revision History Limit:  2
  Selector:
    Match Labels:
      App:  rollout-bg-analysis
  Strategy:
    Blue Green:
      Active Service:  rollout-active-service-analysis
      Post Promotion Analysis:
        Templates:
          Template Name:  test-analysis
  Template:
    Metadata:
      Creation Timestamp:  <nil>
      Labels:
        App:  rollout-bg-analysis
    Spec:
      Containers:
        Image:  nginx:stable
        Name:   nginx-container
        Ports:
          Container Port:  80
        Resources:
Status:
  HPA Replicas:        2
  Available Replicas:  2
  Blue Green:
    Active Selector:  6bcfbc585f
  Canary:
  Conditions:
    Last Transition Time:  2020-10-10T02:25:07Z
    Last Update Time:      2020-10-10T02:25:07Z
    Message:               Rollout has minimum availability
    Reason:                AvailableReason
    Status:                True
    Type:                  Available
    Last Transition Time:  2020-10-10T02:25:02Z
    Last Update Time:      2020-10-10T02:27:13Z
    Message:               ReplicaSet "rollout-bg-test-analysis-6bcfbc585f" has successfully progressed.
    Reason:                NewReplicaSetAvailable
    Status:                True
    Type:                  Progressing
  Current Pod Hash:        6bcfbc585f
  Observed Generation:     786976f646
  Ready Replicas:          2
  Replicas:                2
  Selector:                app=rollout-bg-analysis,rollouts-pod-template-hash=6bcfbc585f
  Stable RS:               6bcfbc585f
  Updated Replicas:        2
Events:
  Type    Reason                   Age    From                 Message
  ----    ------                   ----   ----                 -------
  Normal  ScalingReplicaSet        7m6s   rollouts-controller  Scaled up replica set rollout-bg-test-analysis-766b7567dc to 2
  Normal  SwitchService            7m1s   rollouts-controller  Switched selector for service 'rollout-active-service-analysis' to value '766b7567dc'
  Normal  ScalingReplicaSet        4m56s  rollouts-controller  Scaled up replica set rollout-bg-test-analysis-6bcfbc585f to 2
  Normal  SwitchService            4m55s  rollouts-controller  Switched selector for service 'rollout-active-service-analysis' to value '6bcfbc585f'
  Normal  AnalysisRunStatusChange  4m55s  rollouts-controller  PostPromotion Analysis Run 'rollout-bg-test-analysis-6bcfbc585f-2' Status New: '' Previous: 'NoPreviousStatus'
  Normal  AnalysisRunStatusChange  4m55s  rollouts-controller  PostPromotion Analysis Run 'rollout-bg-test-analysis-6bcfbc585f-2' Status New: 'Running' Previous: ''
  Normal  AnalysisRunStatusChange  4m54s  rollouts-controller  PostPromotion Analysis Run 'rollout-bg-test-analysis-6bcfbc585f-2' Status New: 'Successful' Previous: 'Running'
  Normal  ScalingReplicaSet        4m25s  rollouts-controller  Scaled down replica set rollout-bg-test-analysis-766b7567dc to 0

Rolloutのアップデート (Analysisに失敗した場合)

次にAnalysisに失敗した場合を見てみます。

まずはデプロイ済みのAnalysisTemplateを一部編集し、Analysis実行時に失敗するようにします。

$ kubectl edit analysistemplate test-analysis
analysistemplate.argoproj.io/test-analysis edited

# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"argoproj.io/v1alpha1","kind":"AnalysisTemplate","metadata":{"annotations":{},"name":"test-analysis","namespace":"default"},"spec":{"metrics":[{"name":"test-analysis","provider":{"job":{"spec":{"backoffLimit":1,"template":{"spec":{"containers":[{"args":["exit 0"],"command":["sh","-c"],"image":"alpine:3.8","name":"sleep"}],"restartPolicy":"Never"}}}}}}]}}
  creationTimestamp: "2020-10-10T02:24:45Z"
  generation: 1
  name: test-analysis
  namespace: default
  resourceVersion: "241845"
  selfLink: /apis/argoproj.io/v1alpha1/namespaces/default/analysistemplates/test-analysis
  uid: 2fc98c6c-f40e-4903-a134-a6430aaa0b0d
spec:
  metrics:
  - name: test-analysis
    provider:
      job:
        spec:
          backoffLimit: 1
          template:
            spec:
              containers:
              - args:
                - exit 1 # 変更
                command:
                - sh
                - -c
                image: alpine:3.8
                name: sleep
              restartPolicy: Never

編集が完了したら、先ほどと同様にイメージタグの更新を行い、アップデートの様子を確認します。

# 事前に別のターミナルで実行しておく
$ kubectl argo rollouts get rollout rollout-bg-test-analysis --watch


# イメージタグの更新
$ kubectl argo rollouts set image rollout-bg-test-analysis nginx-container=nginx:1.19
rollout "rollout-bg-test-analysis" image updated


# アップデートの様子を確認する
## アップデート直後
Name:            rollout-bg-test-analysis
Namespace:       default
Status:          ✔ Healthy
Strategy:        BlueGreen
Images:          nginx:stable (active)
Replicas:
  Desired:       2
  Current:       2
  Updated:       2
  Ready:         2
  Available:     2

NAME                                                             KIND         STATUS        AGE    INFO
⟳ rollout-bg-test-analysis                                       Rollout      ✔ Healthy     10m
├──# revision:2
│  ├──⧉ rollout-bg-test-analysis-6bcfbc585f                      ReplicaSet   ✔ Healthy     7m53s  active
│  │  ├──□ rollout-bg-test-analysis-6bcfbc585f-cq2q5             Pod          ✔ Running     7m53s  ready:1/1
│  │  └──□ rollout-bg-test-analysis-6bcfbc585f-dtxdc             Pod          ✔ Running     7m53s  ready:1/1
│  └──α rollout-bg-test-analysis-6bcfbc585f-2                    AnalysisRun  ✔ Successful  7m52s  ✔ 1
│     └──⊞ 471f5e5b-553b-4f94-bae3-cefca88afcd6.test-analysis.1  Job          ✔ Successful  7m52s
└──# revision:1
   └──⧉ rollout-bg-test-analysis-766b7567dc                      ReplicaSet   • ScaledDown  10m


## アップデート開始
Name:            rollout-bg-test-analysis
Namespace:       default
Status:          ◌ Progressing
Strategy:        BlueGreen
Images:          nginx:stable (active)
Replicas:
  Desired:       2
  Current:       2
  Updated:       0
  Ready:         2
  Available:     0

NAME                                                             KIND         STATUS               AGE    INFO
⟳ rollout-bg-test-analysis                                       Rollout      ◌ Progressing        10m
├──# revision:3
│  └──⧉ rollout-bg-test-analysis-6b7c8784cc                      ReplicaSet   ◌ Progressing        0s
│     ├──□ rollout-bg-test-analysis-6b7c8784cc-b29n5             Pod          ◌ Pending            0s     ready:0/1
│     └──□ rollout-bg-test-analysis-6b7c8784cc-ztnjn             Pod          ◌ ContainerCreating  0s     ready:0/1
├──# revision:2
│  ├──⧉ rollout-bg-test-analysis-6bcfbc585f                      ReplicaSet   ✔ Healthy            7m56s  active
│  │  ├──□ rollout-bg-test-analysis-6bcfbc585f-cq2q5             Pod          ✔ Running            7m56s  ready:1/1
│  │  └──□ rollout-bg-test-analysis-6bcfbc585f-dtxdc             Pod          ✔ Running            7m56s  ready:1/1
│  └──α rollout-bg-test-analysis-6bcfbc585f-2                    AnalysisRun  ✔ Successful         7m55s  ✔ 1
│     └──⊞ 471f5e5b-553b-4f94-bae3-cefca88afcd6.test-analysis.1  Job          ✔ Successful         7m55s
└──# revision:1
   └──⧉ rollout-bg-test-analysis-766b7567dc                      ReplicaSet   • ScaledDown         10m


## 切り替えの完了とAnalysisの開始
## revision 3のReplicaSetがactiveになっている
Name:            rollout-bg-test-analysis
Namespace:       default
Status:          ◌ Progressing
Strategy:        BlueGreen
Images:          nginx:1.19 (active)
                 nginx:stable
Replicas:
  Desired:       2
  Current:       4
  Updated:       2
  Ready:         4
  Available:     2

NAME                                                             KIND         STATUS         AGE    INFO
⟳ rollout-bg-test-analysis                                       Rollout      ◌ Progressing  10m
├──# revision:3
│  ├──⧉ rollout-bg-test-analysis-6b7c8784cc                      ReplicaSet   ✔ Healthy      0s     active
│  │  ├──□ rollout-bg-test-analysis-6b7c8784cc-b29n5             Pod          ✔ Running      0s     ready:1/1
│  │  └──□ rollout-bg-test-analysis-6b7c8784cc-ztnjn             Pod          ✔ Running      0s     ready:1/1
│  └──α rollout-bg-test-analysis-6b7c8784cc-3                    AnalysisRun  ◌ Running      0s
│     └──⊞ 0208d4a6-ee21-4ec5-b969-4b75b6784a4b.test-analysis.1  Job          ◌ Running      0s
├──# revision:2
│  ├──⧉ rollout-bg-test-analysis-6bcfbc585f                      ReplicaSet   ✔ Healthy      7m57s  delay:30s
│  │  ├──□ rollout-bg-test-analysis-6bcfbc585f-cq2q5             Pod          ✔ Running      7m57s  ready:1/1
│  │  └──□ rollout-bg-test-analysis-6bcfbc585f-dtxdc             Pod          ✔ Running      7m57s  ready:1/1
│  └──α rollout-bg-test-analysis-6bcfbc585f-2                    AnalysisRun  ✔ Successful   7m56s  ✔ 1
│     └──⊞ 471f5e5b-553b-4f94-bae3-cefca88afcd6.test-analysis.1  Job          ✔ Successful   7m56s
└──# revision:1
   └──⧉ rollout-bg-test-analysis-766b7567dc                      ReplicaSet   • ScaledDown   10m


## Analysis完了(失敗)
Name:            rollout-bg-test-analysis
Namespace:       default
Status:          ◌ Progressing
Strategy:        BlueGreen
Images:          nginx:1.19 (active)
                 nginx:stable
Replicas:
  Desired:       2
  Current:       4
  Updated:       2
  Ready:         4
  Available:     2

NAME                                                             KIND         STATUS         AGE   INFO
⟳ rollout-bg-test-analysis                                       Rollout      ◌ Progressing  10m
├──# revision:3
│  ├──⧉ rollout-bg-test-analysis-6b7c8784cc                      ReplicaSet   ✔ Healthy      12s   active
│  │  ├──□ rollout-bg-test-analysis-6b7c8784cc-b29n5             Pod          ✔ Running      12s   ready:1/1
│  │  └──□ rollout-bg-test-analysis-6b7c8784cc-ztnjn             Pod          ✔ Running      12s   ready:1/1
│  └──α rollout-bg-test-analysis-6b7c8784cc-3                    AnalysisRun  ✖ Failed       11s   ✖ 1
│     └──⊞ 0208d4a6-ee21-4ec5-b969-4b75b6784a4b.test-analysis.1  Job          ✖ Failed       11s
├──# revision:2
│  ├──⧉ rollout-bg-test-analysis-6bcfbc585f                      ReplicaSet   ✔ Healthy      8m9s  delay:18s
│  │  ├──□ rollout-bg-test-analysis-6bcfbc585f-cq2q5             Pod          ✔ Running      8m9s  ready:1/1
│  │  └──□ rollout-bg-test-analysis-6bcfbc585f-dtxdc             Pod          ✔ Running      8m9s  ready:1/1
│  └──α rollout-bg-test-analysis-6bcfbc585f-2                    AnalysisRun  ✔ Successful   8m8s  ✔ 1
│     └──⊞ 471f5e5b-553b-4f94-bae3-cefca88afcd6.test-analysis.1  Job          ✔ Successful   8m8s
└──# revision:1
   └──⧉ rollout-bg-test-analysis-766b7567dc                      ReplicaSet   • ScaledDown   10m


## 完了後
## revision 2のReplicaSetがactiveとなっている
Name:            rollout-bg-test-analysis
Namespace:       default
Status:          ✖ Degraded
Strategy:        BlueGreen
Images:          nginx:1.19
                 nginx:stable (active)
Replicas:
  Desired:       2
  Current:       4
  Updated:       2
  Ready:         4
  Available:     2

NAME                                                             KIND         STATUS        AGE   INFO
⟳ rollout-bg-test-analysis                                       Rollout      ✖ Degraded    10m
├──# revision:3
│  ├──⧉ rollout-bg-test-analysis-6b7c8784cc                      ReplicaSet   ✔ Healthy     12s
│  │  ├──□ rollout-bg-test-analysis-6b7c8784cc-b29n5             Pod          ✔ Running     12s   ready:1/1
│  │  └──□ rollout-bg-test-analysis-6b7c8784cc-ztnjn             Pod          ✔ Running     12s   ready:1/1
│  └──α rollout-bg-test-analysis-6b7c8784cc-3                    AnalysisRun  ✖ Failed      11s   ✖ 1
│     └──⊞ 0208d4a6-ee21-4ec5-b969-4b75b6784a4b.test-analysis.1  Job          ✖ Failed      11s
├──# revision:2
│  ├──⧉ rollout-bg-test-analysis-6bcfbc585f                      ReplicaSet   ✔ Healthy     8m9s  active
│  │  ├──□ rollout-bg-test-analysis-6bcfbc585f-cq2q5             Pod          ✔ Running     8m9s  ready:1/1
│  │  └──□ rollout-bg-test-analysis-6bcfbc585f-dtxdc             Pod          ✔ Running     8m9s  ready:1/1
│  └──α rollout-bg-test-analysis-6bcfbc585f-2                    AnalysisRun  ✔ Successful  8m8s  ✔ 1
│     └──⊞ 471f5e5b-553b-4f94-bae3-cefca88afcd6.test-analysis.1  Job          ✔ Successful  8m8s
└──# revision:1
   └──⧉ rollout-bg-test-analysis-766b7567dc                      ReplicaSet   • ScaledDown  10m

上記の通り、新しいバージョンがデプロイされ、一度はそちらのReplicaSetがactiveになったものの、AnalysisRunが失敗したため、古いバージョンがactiveになったこと、またRolloutのStatusがDegrededの状態となることが確認できました。

RolloutのStatusをDegrededからHealthyに戻すには、元の設定(ここではAnalysisTemplateの修正)に戻すよう再デプロイする必要があります。

なお、完了後のリソースは以下のようになります。AnalysisRunFailedであること、また失敗したJobが残っていることなどが確認できます。

$ kubectl get pods
NAME                                                         READY   STATUS      RESTARTS   AGE
0208d4a6-ee21-4ec5-b969-4b75b6784a4b.test-analysis.1-fk7hn   0/1     Error       0          7m23s
0208d4a6-ee21-4ec5-b969-4b75b6784a4b.test-analysis.1-mdfzs   0/1     Error       0          7m25s
471f5e5b-553b-4f94-bae3-cefca88afcd6.test-analysis.1-8hz2s   0/1     Completed   0          15m
rollout-bg-test-analysis-6b7c8784cc-b29n5                    1/1     Running     0          7m26s
rollout-bg-test-analysis-6b7c8784cc-ztnjn                    1/1     Running     0          7m26s
rollout-bg-test-analysis-6bcfbc585f-cq2q5                    1/1     Running     0          15m
rollout-bg-test-analysis-6bcfbc585f-dtxdc                    1/1     Running     0          15m

$ kubectl get analysisrun
NAME                                    STATUS
rollout-bg-test-analysis-6b7c8784cc-3   Failed
rollout-bg-test-analysis-6bcfbc585f-2   Successful

$ kubectl get rollout
NAME                       DESIRED   CURRENT   UP-TO-DATE   AVAILABLE
rollout-bg-test-analysis   2         4         2            2


$ kubectl describe analysisrun rollout-bg-test-analysis-6b7c8784cc-3
Name:         rollout-bg-test-analysis-6b7c8784cc-3
Namespace:    default
Labels:       rollout-type=PostPromotion
              rollouts-pod-template-hash=6b7c8784cc
Annotations:  rollout.argoproj.io/revision: 3
API Version:  argoproj.io/v1alpha1
Kind:         AnalysisRun
Metadata:
  Creation Timestamp:  2020-10-10T02:35:10Z
  Generation:          3
  Owner References:
    API Version:           argoproj.io/v1alpha1
    Block Owner Deletion:  true
    Controller:            true
    Kind:                  Rollout
    Name:                  rollout-bg-test-analysis
    UID:                   e9ba7057-29db-491a-8618-13180c406fef
  Resource Version:        243985
  Self Link:               /apis/argoproj.io/v1alpha1/namespaces/default/analysisruns/rollout-bg-test-analysis-6b7c8784cc-3
  UID:                     0208d4a6-ee21-4ec5-b969-4b75b6784a4b
Spec:
  Metrics:
    Name:  test-analysis
    Provider:
      Job:
        Metadata:
          Creation Timestamp:  <nil>
        Spec:
          Backoff Limit:  1
          Template:
            Metadata:
              Creation Timestamp:  <nil>
            Spec:
              Containers:
                Args:
                  exit 1
                Command:
                  sh
                  -c
                Image:  alpine:3.8
                Name:   sleep
                Resources:
              Restart Policy:  Never
Status:
  Message:  metric "test-analysis" assessed Failed due to failed (1) > failureLimit (0)
  Metric Results:
    Count:   1
    Failed:  1
    Measurements:
      Finished At:  2020-10-10T02:35:22Z
      Metadata:
        Job - Name:  0208d4a6-ee21-4ec5-b969-4b75b6784a4b.test-analysis.1
      Phase:         Failed
      Started At:    2020-10-10T02:35:10Z
    Name:            test-analysis
    Phase:           Failed
  Phase:             Failed
  Started At:        2020-10-10T02:35:10Z
Events:
  Type     Reason  Age    From                 Message
  ----     ------  ----   ----                 -------
  Warning  Failed  8m27s  rollouts-controller  metric 'test-analysis' completed Failed
  Warning  Failed  8m27s  rollouts-controller  analysis completed Failed


$ kubectl describe rollout rollout-bg-test-analysis
Name:         rollout-bg-test-analysis
Namespace:    default
Labels:       <none>
Annotations:  rollout.argoproj.io/revision: 3
API Version:  argoproj.io/v1alpha1
Kind:         Rollout
Metadata:
  Creation Timestamp:  2020-10-10T02:25:02Z
  Generation:          27
  Resource Version:    244302
  Self Link:           /apis/argoproj.io/v1alpha1/namespaces/default/rollouts/rollout-bg-test-analysis
  UID:                 e9ba7057-29db-491a-8618-13180c406fef
Spec:
  Replicas:                2
  Revision History Limit:  2
  Selector:
    Match Labels:
      App:  rollout-bg-analysis
  Strategy:
    Blue Green:
      Active Service:  rollout-active-service-analysis
      Post Promotion Analysis:
        Templates:
          Template Name:  test-analysis
  Template:
    Metadata:
      Creation Timestamp:  <nil>
      Labels:
        App:  rollout-bg-analysis
    Spec:
      Containers:
        Image:  nginx:1.19
        Name:   nginx-container
        Ports:
          Container Port:  80
        Resources:
Status:
  HPA Replicas:        2
  Abort:               true
  Aborted At:          2020-10-10T02:37:02Z
  Available Replicas:  2
  Blue Green:
    Active Selector:              6bcfbc585f
    Post Promotion Analysis Run:  rollout-bg-test-analysis-6b7c8784cc-3
    Post Promotion Analysis Run Status:
      Message:  metric "test-analysis" assessed Failed due to failed (1) > failureLimit (0)
      Name:     rollout-bg-test-analysis-6b7c8784cc-3
      Status:   Failed
  Canary:
  Conditions:
    Last Transition Time:  2020-10-10T02:25:07Z
    Last Update Time:      2020-10-10T02:25:07Z
    Message:               Rollout has minimum availability
    Reason:                AvailableReason
    Status:                True
    Type:                  Available
    Last Transition Time:  2020-10-10T02:35:22Z
    Last Update Time:      2020-10-10T02:35:22Z
    Message:               metric "test-analysis" assessed Failed due to failed (1) > failureLimit (0)
    Reason:                RolloutAborted
    Status:                False
    Type:                  Progressing
  Current Pod Hash:        6b7c8784cc
  Observed Generation:     57cf5bd85b
  Ready Replicas:          4
  Replicas:                4
  Selector:                app=rollout-bg-analysis,rollouts-pod-template-hash=6bcfbc585f
  Stable RS:               6bcfbc585f
  Updated Replicas:        2
Events:
  Type     Reason                   Age                  From                 Message
  ----     ------                   ----                 ----                 -------
  Normal   ScalingReplicaSet        19m                  rollouts-controller  Scaled up replica set rollout-bg-test-analysis-766b7567dc to 2
  Normal   SwitchService            19m                  rollouts-controller  Switched selector for service 'rollout-active-service-analysis' to value '766b7567dc'
  Normal   ScalingReplicaSet        17m                  rollouts-controller  Scaled up replica set rollout-bg-test-analysis-6bcfbc585f to 2
  Normal   AnalysisRunStatusChange  17m                  rollouts-controller  PostPromotion Analysis Run 'rollout-bg-test-analysis-6bcfbc585f-2' Status New: 'Running' Previous: ''
  Normal   AnalysisRunStatusChange  17m                  rollouts-controller  PostPromotion Analysis Run 'rollout-bg-test-analysis-6bcfbc585f-2' Status New: '' Previous: 'NoPreviousStatus'
  Normal   AnalysisRunStatusChange  17m                  rollouts-controller  PostPromotion Analysis Run 'rollout-bg-test-analysis-6bcfbc585f-2' Status New: 'Successful' Previous: 'Running'
  Normal   ScalingReplicaSet        17m                  rollouts-controller  Scaled down replica set rollout-bg-test-analysis-766b7567dc to 0
  Normal   ScalingReplicaSet        9m51s                rollouts-controller  Scaled up replica set rollout-bg-test-analysis-6b7c8784cc to 2
  Normal   SwitchService            9m50s                rollouts-controller  Switched selector for service 'rollout-active-service-analysis' to value '6b7c8784cc'
  Normal   AnalysisRunStatusChange  9m50s                rollouts-controller  PostPromotion Analysis Run 'rollout-bg-test-analysis-6b7c8784cc-3' Status New: '' Previous: 'NoPreviousStatus'
  Normal   AnalysisRunStatusChange  9m50s                rollouts-controller  PostPromotion Analysis Run 'rollout-bg-test-analysis-6b7c8784cc-3' Status New: 'Running' Previous: ''
  Normal   SwitchService            9m38s (x2 over 17m)  rollouts-controller  Switched selector for service 'rollout-active-service-analysis' to value '6bcfbc585f'
  Warning  AnalysisRunStatusChange  9m38s                rollouts-controller  PostPromotion Analysis Run 'rollout-bg-test-analysis-6b7c8784cc-3' Status New: 'Failed' Previous: 'Running'

実際のアップデート時に失敗しそうなケースを見てみる

上記ではAnalysisTemplateを利用し、条件を満たさない場合に自動的にロールバックする様子を見ました。ここからは、実際にKubernetes上で動かすアプリケーションに対してアップデートを行った時、アップデートが失敗する原因となりうる2つのケースについて、追検証をしてみました。

コンテナ起動に失敗した場合

1つ目は、コンテナの起動に失敗した場合です。今回は、わざと起動に失敗するようなコンテナイメージを用意し、イメージ更新時にそのイメージを指定して、どのような挙動を取るかを確認しました。

コンテナの起動に失敗するように、以下のDockerfileを利用しました。ここでは、存在しないファイルに対してheadコマンドを実行しています。

Dockerfile

FROM nginx:latest

CMD head /foo/bar

Dockerfileを用いてビルドを行った後、Amazon ECRへプッシュをして、EKSからそのイメージを利用する形で検証をしました。

# コンテナイメージのビルド・Push
$ docker build -t test/test03:fail .
$ docker tag test/test03:fail 111111111111.dkr.ecr.ap-northeast-1.amazonaws.com/test/test03:fail
$ docker push 111111111111.dkr.ecr.ap-northeast-1.amazonaws.com/test/test03:fail

また、起動に成功するようなコンテナイメージも、合わせて用意しておきます。

Dockerfile

FROM nginx:latest
# コンテナイメージのビルド・Push
$ docker build -t test/test03:success .
$ docker tag test/test03:success 111111111111.dkr.ecr.ap-northeast-1.amazonaws.com/test/test03:success
$ docker push 111111111111.dkr.ecr.ap-northeast-1.amazonaws.com/test/test03:success

今回の検証で利用したマニフェストファイルは以下の通りです。

rollout-bg-test-start-fail.yml

apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: rollout-bg-test-start-fail
spec:
  replicas: 2
  revisionHistoryLimit: 2
  selector:
    matchLabels:
      app: rollout-bg-analysis-start-fail
  template:
    metadata:
      labels:
        app: rollout-bg-analysis-start-fail
    spec:
      containers:
        - name: nginx-container
          image: 111111111111.dkr.ecr.ap-northeast-1.amazonaws.com/test/test03:success
          ports:
          - containerPort: 80
  strategy:
    blueGreen:
      activeService: rollout-active-service-start-fail
      postPromotionAnalysis:
        templates:
        - templateName: test-analysis

rollout-active-service-start-fail.yml

apiVersion: v1
kind: Service
metadata:
  name: rollout-active-service-start-fail
spec:
  ports:
  - port: 8080
    targetPort: 80
    protocol: TCP
  selector:
    app: rollout-bg-analysis-start-fail

まずは上記Yamlファイル、そしてtest-analysisを含むAnalysisTemplateを作成しておきます。

$ kubectl apply -f test-analysistemp.yml
analysistemplate.argoproj.io/test-analysis created

$ kubectl apply -f rollout-active-service-start-fail.yml
service/rollout-active-service-start-fail created

$ kubectl apply -f rollout-bg-test-start-fail.yml
rollout.argoproj.io/rollout-bg-test-start-fail created


# デプロイ後の確認
$ kubectl get svc
NAME                                TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
kubernetes                          ClusterIP   10.100.0.1      <none>        443/TCP    85m
rollout-active-service-start-fail   ClusterIP   10.100.32.113   <none>        8080/TCP   104s

$ kubectl get pods
NAME                                         READY   STATUS    RESTARTS   AGE
rollout-bg-test-start-fail-8cdb4dcc6-cnjlt   1/1     Running   0          13s
rollout-bg-test-start-fail-8cdb4dcc6-phsgq   1/1     Running   0          13s

$ kubectl get rollout
NAME                         DESIRED   CURRENT   UP-TO-DATE   AVAILABLE
rollout-bg-test-start-fail   2         2         2            2


$ kubectl argo rollouts get rollout rollout-bg-test-start-fail
Name:            rollout-bg-test-start-fail
Namespace:       default
Status:          ✔ Healthy
Strategy:        BlueGreen
Images:          111111111111.dkr.ecr.ap-northeast-1.amazonaws.com/test/test03:success (active)
Replicas:
  Desired:       2
  Current:       2
  Updated:       2
  Ready:         2
  Available:     2

NAME                                                   KIND        STATUS     AGE  INFO
⟳ rollout-bg-test-start-fail                           Rollout     ✔ Healthy  11m
└──# revision:1
   └──⧉ rollout-bg-test-start-fail-8cdb4dcc6           ReplicaSet  ✔ Healthy  11m  active
      ├──□ rollout-bg-test-start-fail-8cdb4dcc6-cnjlt  Pod         ✔ Running  11m  ready:1/1
      └──□ rollout-bg-test-start-fail-8cdb4dcc6-phsgq  Pod         ✔ Running  11m  ready:1/1

次に、イメージの更新を実行し、アップデートの様子を確認します。

# 別のターミナルで実行
$ kubectl argo rollouts get rollout rollout-bg-test-start-fail --watch


# イメージの更新
$ kubectl argo rollouts set image rollout-bg-test-start-fail nginx-container=111111111111.dkr.ecr.ap-northeast-1.amazonaws.com/test/test03:fail
rollout "rollout-bg-test-start-fail" image updated


# アップデートの様子を確認
## アップデート直後
Name:            rollout-bg-test-start-fail
Namespace:       default
Status:          ✔ Healthy
Strategy:        BlueGreen
Images:          111111111111.dkr.ecr.ap-northeast-1.amazonaws.com/test/test03:success (active)
Replicas:
  Desired:       2
  Current:       2
  Updated:       2
  Ready:         2
  Available:     2

NAME                                                   KIND        STATUS     AGE  INFO
⟳ rollout-bg-test-start-fail                           Rollout     ✔ Healthy  12m
└──# revision:1
   └──⧉ rollout-bg-test-start-fail-8cdb4dcc6           ReplicaSet  ✔ Healthy  12m  active
      ├──□ rollout-bg-test-start-fail-8cdb4dcc6-cnjlt  Pod         ✔ Running  12m  ready:1/1
      └──□ rollout-bg-test-start-fail-8cdb4dcc6-phsgq  Pod         ✔ Running  12m  ready:1/1


## 新規コンテナの作成開始
Name:            rollout-bg-test-start-fail
Namespace:       default
Status:          ◌ Progressing
Strategy:        BlueGreen
Images:          111111111111.dkr.ecr.ap-northeast-1.amazonaws.com/test/test03:fail
                 111111111111.dkr.ecr.ap-northeast-1.amazonaws.com/test/test03:success (active)
Replicas:
  Desired:       2
  Current:       4
  Updated:       2
  Ready:         2
  Available:     2

NAME                                                    KIND        STATUS               AGE  INFO
⟳ rollout-bg-test-start-fail                            Rollout     ◌ Progressing        12m
├──# revision:2
│  └──⧉ rollout-bg-test-start-fail-7fb74fd5f5           ReplicaSet  ◌ Progressing        0s
│     ├──□ rollout-bg-test-start-fail-7fb74fd5f5-948tm  Pod         ◌ ContainerCreating  0s   ready:0/1
│     └──□ rollout-bg-test-start-fail-7fb74fd5f5-mr4n4  Pod         ◌ ContainerCreating  0s   ready:0/1
└──# revision:1
   └──⧉ rollout-bg-test-start-fail-8cdb4dcc6            ReplicaSet  ✔ Healthy            12m  active
      ├──□ rollout-bg-test-start-fail-8cdb4dcc6-cnjlt   Pod         ✔ Running            12m  ready:1/1
      └──□ rollout-bg-test-start-fail-8cdb4dcc6-phsgq   Pod         ✔ Running            12m  ready:1/1


## 作成に失敗
Name:            rollout-bg-test-start-fail
Namespace:       default
Status:          ◌ Progressing
Strategy:        BlueGreen
Images:          111111111111.dkr.ecr.ap-northeast-1.amazonaws.com/test/test03:fail
                 111111111111.dkr.ecr.ap-northeast-1.amazonaws.com/test/test03:success (active)
Replicas:
  Desired:       2
  Current:       4
  Updated:       2
  Ready:         2
  Available:     2

NAME                                                    KIND        STATUS               AGE  INFO
⟳ rollout-bg-test-start-fail                            Rollout     ◌ Progressing        12m
├──# revision:2
│  └──⧉ rollout-bg-test-start-fail-7fb74fd5f5           ReplicaSet  ◌ Progressing        1s
│     ├──□ rollout-bg-test-start-fail-7fb74fd5f5-948tm  Pod         ⚠ Error              1s   ready:0/1
│     └──□ rollout-bg-test-start-fail-7fb74fd5f5-mr4n4  Pod         ◌ ContainerCreating  1s   ready:0/1
└──# revision:1
   └──⧉ rollout-bg-test-start-fail-8cdb4dcc6            ReplicaSet  ✔ Healthy            12m  active
      ├──□ rollout-bg-test-start-fail-8cdb4dcc6-cnjlt   Pod         ✔ Running            12m  ready:1/1
      └──□ rollout-bg-test-start-fail-8cdb4dcc6-phsgq   Pod         ✔ Running            12m  ready:1/1
Name:            rollout-bg-test-start-fail
Namespace:       default
Status:          ◌ Progressing
Strategy:        BlueGreen
Images:          111111111111.dkr.ecr.ap-northeast-1.amazonaws.com/test/test03:fail
                 111111111111.dkr.ecr.ap-northeast-1.amazonaws.com/test/test03:success (active)
Replicas:
  Desired:       2
  Current:       4
  Updated:       2
  Ready:         2
  Available:     2

NAME                                                    KIND        STATUS         AGE  INFO
⟳ rollout-bg-test-start-fail                            Rollout     ◌ Progressing  12m
├──# revision:2
│  └──⧉ rollout-bg-test-start-fail-7fb74fd5f5           ReplicaSet  ◌ Progressing  1s
│     ├──□ rollout-bg-test-start-fail-7fb74fd5f5-948tm  Pod         ⚠ Error        1s   ready:0/1
│     └──□ rollout-bg-test-start-fail-7fb74fd5f5-mr4n4  Pod         ⚠ Error        1s   ready:0/1
└──# revision:1
   └──⧉ rollout-bg-test-start-fail-8cdb4dcc6            ReplicaSet  ✔ Healthy      12m  active
      ├──□ rollout-bg-test-start-fail-8cdb4dcc6-cnjlt   Pod         ✔ Running      12m  ready:1/1
      └──□ rollout-bg-test-start-fail-8cdb4dcc6-phsgq   Pod         ✔ Running      12m  ready:1/1

Name:            rollout-bg-test-start-fail
Namespace:       default
Status:          ◌ Progressing
Strategy:        BlueGreen
Images:          111111111111.dkr.ecr.ap-northeast-1.amazonaws.com/test/test03:fail
                 111111111111.dkr.ecr.ap-northeast-1.amazonaws.com/test/test03:success (active)
Replicas:
  Desired:       2
  Current:       4
  Updated:       2
  Ready:         2
  Available:     2

NAME                                                    KIND        STATUS              AGE  INFO
⟳ rollout-bg-test-start-fail                            Rollout     ◌ Progressing       12m
├──# revision:2
│  └──⧉ rollout-bg-test-start-fail-7fb74fd5f5           ReplicaSet  ◌ Progressing       3s
│     ├──□ rollout-bg-test-start-fail-7fb74fd5f5-948tm  Pod         ✖ CrashLoopBackOff  3s   ready:0/1,restarts:1
│     └──□ rollout-bg-test-start-fail-7fb74fd5f5-mr4n4  Pod         ⚠ Error             3s   ready:0/1,restarts:1
└──# revision:1
   └──⧉ rollout-bg-test-start-fail-8cdb4dcc6            ReplicaSet  ✔ Healthy           12m  active
      ├──□ rollout-bg-test-start-fail-8cdb4dcc6-cnjlt   Pod         ✔ Running           12m  ready:1/1
      └──□ rollout-bg-test-start-fail-8cdb4dcc6-phsgq   Pod         ✔ Running           12m  ready:1/1
Name:            rollout-bg-test-start-fail
Namespace:       default
Status:          ◌ Progressing
Strategy:        BlueGreen
Images:          111111111111.dkr.ecr.ap-northeast-1.amazonaws.com/test/test03:fail
                 111111111111.dkr.ecr.ap-northeast-1.amazonaws.com/test/test03:success (active)
Replicas:
  Desired:       2
  Current:       4
  Updated:       2
  Ready:         2
  Available:     2

NAME                                                    KIND        STATUS              AGE  INFO
⟳ rollout-bg-test-start-fail                            Rollout     ◌ Progressing       12m
├──# revision:2
│  └──⧉ rollout-bg-test-start-fail-7fb74fd5f5           ReplicaSet  ◌ Progressing       3s
│     ├──□ rollout-bg-test-start-fail-7fb74fd5f5-948tm  Pod         ✖ CrashLoopBackOff  3s   ready:0/1,restarts:1
│     └──□ rollout-bg-test-start-fail-7fb74fd5f5-mr4n4  Pod         ✖ CrashLoopBackOff  3s   ready:0/1,restarts:1
└──# revision:1
   └──⧉ rollout-bg-test-start-fail-8cdb4dcc6            ReplicaSet  ✔ Healthy           12m  active
      ├──□ rollout-bg-test-start-fail-8cdb4dcc6-cnjlt   Pod         ✔ Running           12m  ready:1/1
      └──□ rollout-bg-test-start-fail-8cdb4dcc6-phsgq   Pod         ✔ Running           12m  ready:1/1


## しばらくするとDegradedになる
Name:            rollout-bg-test-start-fail
Namespace:       default
Status:          ✖ Degraded
Strategy:        BlueGreen
Images:          111111111111.dkr.ecr.ap-northeast-1.amazonaws.com/test/test03:fail
                 111111111111.dkr.ecr.ap-northeast-1.amazonaws.com/test/test03:success (active)
Replicas:
  Desired:       2
  Current:       4
  Updated:       2
  Ready:         2
  Available:     2

NAME                                                    KIND        STATUS              AGE  INFO
⟳ rollout-bg-test-start-fail                            Rollout     ✖ Degraded          25m
├──# revision:2
│  └──⧉ rollout-bg-test-start-fail-7fb74fd5f5           ReplicaSet  ◌ Progressing       13m
│     ├──□ rollout-bg-test-start-fail-7fb74fd5f5-948tm  Pod         ✖ CrashLoopBackOff  13m  ready:0/1,restarts:7
│     └──□ rollout-bg-test-start-fail-7fb74fd5f5-mr4n4  Pod         ✖ CrashLoopBackOff  13m  ready:0/1,restarts:7
└──# revision:1
   └──⧉ rollout-bg-test-start-fail-8cdb4dcc6            ReplicaSet  ✔ Healthy           25m  active
      ├──□ rollout-bg-test-start-fail-8cdb4dcc6-cnjlt   Pod         ✔ Running           25m  ready:1/1
      └──□ rollout-bg-test-start-fail-8cdb4dcc6-phsgq   Pod         ✔ Running           25m  ready:1/1

上記の通り、コンテナの起動に失敗した場合は、Analysisが実行される前に起動に失敗するため、新しいバージョンへの切り替えは発生しませんでした。StatusはDegradedとなってしまいますが、コンテナの起動に失敗した場合は、アプリケーションの稼働時間に対しての影響はなさそうに見えます。

なお、RolloutのStatusはDegradedになるため、元のコンテナイメージを用いて再デプロイをすることでHealthyにすることができます。

Liveness Probeに失敗し続けた場合

次にLiveness Probeで失敗する場合について見ていきます。今回は以下のようなマニフェストファイルを用意し、あとからLiveness Probeの条件を変更することで、Probeに失敗する状況を作っています。

なお、今回はLiveness Probeの設定(initialDelaySeconds periodSeconds)により、Liveness Probeより先にAnalysisRunが起動する形となっております。

rollout-bg-test-liveness-fail.yml

apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: rollout-bg-test-liveness-fail
spec:
  replicas: 2
  revisionHistoryLimit: 2
  selector:
    matchLabels:
      app: rollout-bg-analysis-liveness-fail
  template:
    metadata:
      labels:
        app: rollout-bg-analysis-liveness-fail
    spec:
      containers:
        - name: nginx-container
          image: 111111111111.dkr.ecr.ap-northeast-1.amazonaws.com/test/test03:success
          ports:
          - containerPort: 80
          livenessProbe:
            tcpSocket:
              port: 80
            initialDelaySeconds: 5
            periodSeconds: 5
            timeoutSeconds: 1
            successThreshold: 1
            failureThreshold: 1
  strategy:
    blueGreen:
      activeService: rollout-active-service-liveness-fail
      postPromotionAnalysis:
        templates:
        - templateName: test-analysis

rollout-active-service-liveness-fail.yml

apiVersion: v1
kind: Service
metadata:
  name: rollout-active-service-liveness-fail
spec:
  ports:
  - port: 8080
    targetPort: 80
    protocol: TCP
  selector:
    app: rollout-bg-analysis-liveness-fail

上記2つのYamlファイル、そしてtest-analysisを含むAnalysisTemplateを作成しておきます。

$ kubectl apply -f rollout-active-service-liveness-fail.yml
service/rollout-active-service-liveness-fail created

$ kubectl apply -f rollout-bg-test-liveness-fail.yml
rollout.argoproj.io/rollout-bg-test-liveness-fail created

# デプロイ後の確認
$ kubectl get svc
NAME                                   TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
kubernetes                             ClusterIP   10.100.0.1      <none>        443/TCP    150m
rollout-active-service-liveness-fail   ClusterIP   10.100.21.204   <none>        8080/TCP   9m1s

$ kubectl get pods
NAME                                             READY   STATUS    RESTARTS   AGE
rollout-bg-test-liveness-fail-54495f7df4-df2dk   1/1     Running   0          15s
rollout-bg-test-liveness-fail-54495f7df4-svb25   1/1     Running   0          15s

$ kubectl get rollout
NAME                            DESIRED   CURRENT   UP-TO-DATE   AVAILABLE
rollout-bg-test-liveness-fail   2         2         2            2

$ kubectl argo rollouts get rollout rollout-bg-test-liveness-fail
Name:            rollout-bg-test-liveness-fail
Namespace:       default
Status:          ✔ Healthy
Strategy:        BlueGreen
Images:          111111111111.dkr.ecr.ap-northeast-1.amazonaws.com/test/test03:success (active)
Replicas:
  Desired:       2
  Current:       2
  Updated:       2
  Ready:         2
  Available:     2

NAME                                                       KIND        STATUS     AGE    INFO
⟳ rollout-bg-test-liveness-fail                            Rollout     ✔ Healthy  2m37s
└──# revision:1
   └──⧉ rollout-bg-test-liveness-fail-54495f7df4           ReplicaSet  ✔ Healthy  2m36s  active
      ├──□ rollout-bg-test-liveness-fail-54495f7df4-df2dk  Pod         ✔ Running  2m36s  ready:1/1
      └──□ rollout-bg-test-liveness-fail-54495f7df4-svb25  Pod         ✔ Running  2m36s  ready:1/1

次にLiveness Probeの内容を一部変更し、それによるアップデートの推移を確認してみます。

# 別のターミナルで実行
$ kubectl argo rollouts get rollout rollout-bg-test-liveness-fail --watch


# Rolloutの編集
$ kubectl edit rollout rollout-bg-test-liveness-fail
rollout.argoproj.io/rollout-bg-test-liveness-fail edited


# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"argoproj.io/v1alpha1","kind":"Rollout","metadata":{"annotations":{},"name":"rollout-bg-test-liveness-fail","namespace":"default"},"spec":{"replicas":2,"revisionHistoryLimit":2,"selector":{"matchLabels":{"app":"rollout-bg-analysis-liveness-fail"}},"strategy":{"blueGreen":{"activeService":"rollout-active-service-liveness-fail","postPromotionAnalysis":{"templates":[{"templateName":"test-analysis"}]}}},"template":{"metadata":{"labels":{"app":"rollout-bg-analysis-liveness-fail"}},"spec":{"containers":[{"image":"111111111111.dkr.ecr.ap-northeast-1.amazonaws.com/test/test03:success","livenessProbe":{"failureThreshold":1,"initialDelaySeconds":5,"periodSeconds":5,"successThreshold":1,"tcpSocket":{"port":80},"timeoutSeconds":1},"name":"nginx-container","ports":[{"containerPort":80}]}]}}}}
    rollout.argoproj.io/revision: "1"
  creationTimestamp: "2020-10-12T06:52:16Z"
  generation: 6
  name: rollout-bg-test-liveness-fail
  namespace: default
  resourceVersion: "28459"
  selfLink: /apis/argoproj.io/v1alpha1/namespaces/default/rollouts/rollout-bg-test-liveness-fail
  uid: 70fa4d0b-fb3a-46c6-8bfd-321e2d829694
spec:
  replicas: 2
  revisionHistoryLimit: 2
  selector:
    matchLabels:
      app: rollout-bg-analysis-liveness-fail
  strategy:
    blueGreen:
      activeService: rollout-active-service-liveness-fail
      postPromotionAnalysis:
        templates:
        - templateName: test-analysis
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: rollout-bg-analysis-liveness-fail
    spec:
      containers:
      - image: 111111111111.dkr.ecr.ap-northeast-1.amazonaws.com/test/test03:success
        livenessProbe:
          failureThreshold: 1
          initialDelaySeconds: 5
          periodSeconds: 5
          successThreshold: 1
          tcpSocket:
            port: 8080  # 変更
          timeoutSeconds: 1
        name: nginx-container
        ports:
        - containerPort: 80
        resources: {}
status:
  HPAReplicas: 2
  availableReplicas: 2
  blueGreen:
    activeSelector: 54495f7df4
  canary: {}
  conditions:
  - lastTransitionTime: "2020-10-12T06:52:17Z"
    lastUpdateTime: "2020-10-12T06:52:18Z"
    message: ReplicaSet "rollout-bg-test-liveness-fail-54495f7df4" has successfully
      progressed.
    reason: NewReplicaSetAvailable
    status: "True"
    type: Progressing
  - lastTransitionTime: "2020-10-12T06:52:18Z"
    lastUpdateTime: "2020-10-12T06:52:18Z"
    message: Rollout has minimum availability
    reason: AvailableReason
    status: "True"
    type: Available
  currentPodHash: 54495f7df4
  observedGeneration: 75d6d6f664
  readyReplicas: 2
  replicas: 2
  selector: app=rollout-bg-analysis-liveness-fail,rollouts-pod-template-hash=54495f7df4
  stableRS: 54495f7df4
  updatedReplicas: 2


# アップデートの様子を確認
## コンテナの作成開始
Name:            rollout-bg-test-liveness-fail
Namespace:       default
Status:          ◌ Progressing
Strategy:        BlueGreen
Images:          111111111111.dkr.ecr.ap-northeast-1.amazonaws.com/test/test03:success (active)
Replicas:
  Desired:       2
  Current:       4
  Updated:       2
  Ready:         2
  Available:     2

NAME                                                       KIND        STATUS               AGE  INFO
⟳ rollout-bg-test-liveness-fail                            Rollout     ◌ Progressing        14m
├──# revision:2
│  └──⧉ rollout-bg-test-liveness-fail-7bb5898d6            ReplicaSet  ◌ Progressing        0s
│     ├──□ rollout-bg-test-liveness-fail-7bb5898d6-tw8lt   Pod         ◌ ContainerCreating  0s   ready:0/1
│     └──□ rollout-bg-test-liveness-fail-7bb5898d6-wb944   Pod         ◌ ContainerCreating  0s   ready:0/1
└──# revision:1
   └──⧉ rollout-bg-test-liveness-fail-54495f7df4           ReplicaSet  ✔ Healthy            14m  active
      ├──□ rollout-bg-test-liveness-fail-54495f7df4-df2dk  Pod         ✔ Running            14m  ready:1/1
      └──□ rollout-bg-test-liveness-fail-54495f7df4-svb25  Pod         ✔ Running            14m  ready:1/1


## コンテナの作成完了とAnalysisの開始
Name:            rollout-bg-test-liveness-fail
Namespace:       default
Status:          ◌ Progressing
Strategy:        BlueGreen
Images:          111111111111.dkr.ecr.ap-northeast-1.amazonaws.com/test/test03:success (active)
Replicas:
  Desired:       2
  Current:       4
  Updated:       2
  Ready:         2
  Available:     2

NAME                                                             KIND         STATUS         AGE  INFO
⟳ rollout-bg-test-liveness-fail                                  Rollout      ◌ Progressing  14m
├──# revision:2
│  ├──⧉ rollout-bg-test-liveness-fail-7bb5898d6                  ReplicaSet   ✔ Healthy      1s   active
│  │  ├──□ rollout-bg-test-liveness-fail-7bb5898d6-tw8lt         Pod          ✔ Running      1s   ready:1/1
│  │  └──□ rollout-bg-test-liveness-fail-7bb5898d6-wb944         Pod          ✔ Running      1s   ready:1/1
│  └──α rollout-bg-test-liveness-fail-7bb5898d6-2-post           AnalysisRun  ◌ Running      0s
│     └──⊞ 3bf49f08-e851-4185-8cbf-88886c0da2ec.test-analysis.1  Job          ◌ Running      0s
└──# revision:1
   └──⧉ rollout-bg-test-liveness-fail-54495f7df4                 ReplicaSet   ✔ Healthy      14m  delay:29s
      ├──□ rollout-bg-test-liveness-fail-54495f7df4-df2dk        Pod          ✔ Running      14m  ready:1/1
      └──□ rollout-bg-test-liveness-fail-54495f7df4-svb25        Pod          ✔ Running      14m  ready:1/1


## Analysisの完了(成功)
Name:            rollout-bg-test-liveness-fail
Namespace:       default
Status:          ◌ Progressing
Strategy:        BlueGreen
Images:          111111111111.dkr.ecr.ap-northeast-1.amazonaws.com/test/test03:success (active)
Replicas:
  Desired:       2
  Current:       4
  Updated:       2
  Ready:         2
  Available:     2

NAME                                                             KIND         STATUS         AGE  INFO
⟳ rollout-bg-test-liveness-fail                                  Rollout      ◌ Progressing  14m
├──# revision:2
│  ├──⧉ rollout-bg-test-liveness-fail-7bb5898d6                  ReplicaSet   ✔ Healthy      5s   active
│  │  ├──□ rollout-bg-test-liveness-fail-7bb5898d6-tw8lt         Pod          ✔ Running      5s   ready:1/1
│  │  └──□ rollout-bg-test-liveness-fail-7bb5898d6-wb944         Pod          ✔ Running      5s   ready:1/1
│  └──α rollout-bg-test-liveness-fail-7bb5898d6-2-post           AnalysisRun  ✔ Successful   4s   ✔ 1
│     └──⊞ 3bf49f08-e851-4185-8cbf-88886c0da2ec.test-analysis.1  Job          ✔ Successful   4s
└──# revision:1
   └──⧉ rollout-bg-test-liveness-fail-54495f7df4                 ReplicaSet   ✔ Healthy      14m  delay:25s
      ├──□ rollout-bg-test-liveness-fail-54495f7df4-df2dk        Pod          ✔ Running      14m  ready:1/1
      └──□ rollout-bg-test-liveness-fail-54495f7df4-svb25        Pod          ✔ Running      14m  ready:1/1


## コンテナのRestart
Name:            rollout-bg-test-liveness-fail
Namespace:       default
Status:          ◌ Progressing
Strategy:        BlueGreen
Images:          111111111111.dkr.ecr.ap-northeast-1.amazonaws.com/test/test03:success (active)
Replicas:
  Desired:       2
  Current:       4
  Updated:       2
  Ready:         2
  Available:     2

NAME                                                             KIND         STATUS         AGE  INFO
⟳ rollout-bg-test-liveness-fail                                  Rollout      ◌ Progressing  14m
├──# revision:2
│  ├──⧉ rollout-bg-test-liveness-fail-7bb5898d6                  ReplicaSet   ✔ Healthy      8s   active
│  │  ├──□ rollout-bg-test-liveness-fail-7bb5898d6-tw8lt         Pod          ✔ Running      8s   ready:1/1,restarts:1
│  │  └──□ rollout-bg-test-liveness-fail-7bb5898d6-wb944         Pod          ✔ Running      8s   ready:1/1,restarts:1
│  └──α rollout-bg-test-liveness-fail-7bb5898d6-2-post           AnalysisRun  ✔ Successful   7s   ✔ 1
│     └──⊞ 3bf49f08-e851-4185-8cbf-88886c0da2ec.test-analysis.1  Job          ✔ Successful   7s
└──# revision:1
   └──⧉ rollout-bg-test-liveness-fail-54495f7df4                 ReplicaSet   ✔ Healthy      14m  delay:22s
      ├──□ rollout-bg-test-liveness-fail-54495f7df4-df2dk        Pod          ✔ Running      14m  ready:1/1
      └──□ rollout-bg-test-liveness-fail-54495f7df4-svb25        Pod          ✔ Running      14m  ready:1/1


## 以降はコンテナの再起動と失敗を繰り返す
Name:            rollout-bg-test-liveness-fail
Namespace:       default
Status:          ◌ Progressing
Strategy:        BlueGreen
Images:          111111111111.dkr.ecr.ap-northeast-1.amazonaws.com/test/test03:success (active)
Replicas:
  Desired:       2
  Current:       2
  Updated:       2
  Ready:         1
  Available:     1

NAME                                                             KIND         STATUS              AGE  INFO
⟳ rollout-bg-test-liveness-fail                                  Rollout      ◌ Progressing       14m
├──# revision:2
│  ├──⧉ rollout-bg-test-liveness-fail-7bb5898d6                  ReplicaSet   ◌ Progressing       31s  active
│  │  ├──□ rollout-bg-test-liveness-fail-7bb5898d6-tw8lt         Pod          ✔ Running           31s  ready:1/1,restarts:3
│  │  └──□ rollout-bg-test-liveness-fail-7bb5898d6-wb944         Pod          ✖ CrashLoopBackOff  31s  ready:0/1,restarts:2
│  └──α rollout-bg-test-liveness-fail-7bb5898d6-2-post           AnalysisRun  ✔ Successful        30s  ✔ 1
│     └──⊞ 3bf49f08-e851-4185-8cbf-88886c0da2ec.test-analysis.1  Job          ✔ Successful        30s
└──# revision:1
   └──⧉ rollout-bg-test-liveness-fail-54495f7df4                 ReplicaSet   • ScaledDown        14m
      ├──□ rollout-bg-test-liveness-fail-54495f7df4-df2dk        Pod          ◌ Terminating       14m  ready:0/1
      └──□ rollout-bg-test-liveness-fail-54495f7df4-svb25        Pod          ◌ Terminating       14m  ready:0/1




Name:            rollout-bg-test-liveness-fail
Namespace:       default
Status:          ◌ Progressing
Strategy:        BlueGreen
Images:          111111111111.dkr.ecr.ap-northeast-1.amazonaws.com/test/test03:success (active)
Replicas:
  Desired:       2
  Current:       2
  Updated:       2
  Ready:         1
  Available:     1

NAME                                                             KIND         STATUS              AGE  INFO
⟳ rollout-bg-test-liveness-fail                                  Rollout      ◌ Progressing       15m
├──# revision:2
│  ├──⧉ rollout-bg-test-liveness-fail-7bb5898d6                  ReplicaSet   ◌ Progressing       43s  active
│  │  ├──□ rollout-bg-test-liveness-fail-7bb5898d6-tw8lt         Pod          ✖ CrashLoopBackOff  43s  ready:0/1,restarts:3
│  │  └──□ rollout-bg-test-liveness-fail-7bb5898d6-wb944         Pod          ✖ CrashLoopBackOff  43s  ready:0/1,restarts:4
│  └──α rollout-bg-test-liveness-fail-7bb5898d6-2-post           AnalysisRun  ✔ Successful        42s  ✔ 1
│     └──⊞ 3bf49f08-e851-4185-8cbf-88886c0da2ec.test-analysis.1  Job          ✔ Successful        42s
└──# revision:1
   └──⧉ rollout-bg-test-liveness-fail-54495f7df4                 ReplicaSet   • ScaledDown        15m

上記の通り、Liveness Probeに失敗すると、一度はPodが作成され、トラフィックの切り替えも発生しますが、Probeに失敗する限りコンテナのRestartが繰り返される状況となることがわかりました。またRolloutのStatusはProgressingの状態が続き、これをHealthyに戻すには、やはり正常に動くRollout(ここではLiveness Probeの設定を修正したもの)を再デプロイする必要があります。

今回のAnalysisTemplateは、Liveness Probeより先に実行されるようにしており、また実行すれば必ず成功するものだったため、あまり意味のないものでした。一方でAnalysisの実行内容を工夫することで(例えば起動後に一定時間Podへの疎通確認を行うなど?)、この問題を解決することができるかもしれません。またBlue/Green Deploymentを利用する場合prePromotionAnalysisを設定することで、切り替え前の分析を実行することもできます。これにより、切り替え前にLiveness Probeの設定(不備?)によるコンテナの再起動の繰り返しが起きる場合に備え、Analysisを実行して検知をするよう設定することもできるのでは、と考えています。

Prometheus等の監視メトリクスによって問題を検知した場合

今回は検証を行いませんが、Argo RolloutsではAnalysisTemplatePrometheusのメトリクスなどを利用することができます。これにより、新バージョンのデプロイ・リリース完了前後でアプリケーション等に問題が見られた場合に、自動的にロールバックを行うこともできます。

公式ドキュメントでは、以下のようなマニフェストファイルの例が紹介されています。Analysisの成功・失敗の基準(successCondition)と実際のAnalysis(provider.prometheus.query)を定義し、条件を満たさない場合はロールバックを行います。

apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
  name: success-rate
spec:
  args:
  - name: service-name
  - name: prometheus-port
    value: 9090
  metrics:
  - name: success-rate
    successCondition: result[0] >= 0.95
    provider:
      prometheus:
        address: "http://prometheus.example.com:{{args.prometheus-port}}"
        query: |
          sum(irate(
            istio_requests_total{reporter="source",destination_service=~"{{args.service-name}}",response_code!~"5.*"}[5m]
          )) / 
          sum(irate(
            istio_requests_total{reporter="source",destination_service=~"{{args.service-name}}"}[5m]
          ))

参考ドキュメント

Argo Rolloutsに入門する

先日ArgoCDを利用してみましたが、今回は同じArgo Projectの中からArgo Rolloutsを試してみました。Argo Rolloutsを利用することで、より高度なデプロイ・リリースを実現することができます。

リリース方式の違いと特徴について

Argo Rolloutsを利用する前に、Kubernetes上で利用することの多いデリバリー/リリース方式について整理しておきます。なお、記載に当たり、以下のドキュメントを参照いたしました。

※参考ドキュメント:

Rolling Update

KubernetesでPod(Deployment)をデプロイする際に利用できる方法として、デフォルトではRecreate Rolling Updateなどの方法があります。特にアプリケーションのダウンタイムをゼロにするRolling Updateは、様々なアプリケーションで利用されていると思われます。

Rolling Updateでは、稼働中のPodが新しいバージョンに置き換わるまで、一つずつPodがアップグレードされます。新しいバージョンのPodが起動するまでは、それまで稼働していた古いバージョンの残りのPodが通信を受け付け、新しいバージョンのPodが起動すると別の古いバージョンのPodがアップデートを開始します。

Rolling Updateを利用すると、一時的に複数のアプリケーションのバージョンが同居することになりますが、アプリケーション全体としてはダウンタイムが発生することなくアップデートを完了することができます。

※画像:Search ITOperations

rolling-update

Rolling UpdateはKubernetesのDeploymentリソースにてspec.strategy.type: RollingUpdateがデフォルトで設定されています。またアップデート時にPodの数をどのように増減させるかはspec.strategy.rollingUpdate.maxSurge spec.strategy.rollingUpdate.maxUnavailableを設定することでコントロールすることができます。

このようにゼロダウンタイムでのアップデートを実現することのできるRolling Updateですが、いくつかの欠点も抱えています。そのため、大規模な本番環境で採用するにはリスクが高いと考えられています。

異なるバージョンが混在する状況が生まれる

前述の通りRolling Updateでは同じクラスター上に異なるバージョンのPodが共存する状況が発生し、アプリケーションによってはこの状況が望ましくない場合があります。例えば、バージョン間で著しく機能やUIに差分がある場合、ユーザーごとに異なる体験をすることになります。アプリケーションのサポートは2つのバージョンについて把握する必要があり、またユーザーがどちらのバージョンを利用しているか識別できるようにしなければなりません。

また、異なるバージョンが同時に稼働することでN-1 data compatibilityという問題も発生します。これは新しいバージョンで書き込まれたデータが古いバージョンでも読み込めるようにしなければならないことを指しますが、他のデプロイ方法でも同様に問題となる項目となります。

ロールアウトの実行時間が長くなる

Rolling Updateでは基本的に1つずつPodを更新するので、Podが大量に存在する場合はアップデートの完了までに非常に時間がかかる場合があります。前述のmaxSurge maxUnavailableを変更することで、アップデートが完了するまでの時間を短縮することはできますが、クラスターにかかる負荷が増加するなどのデメリットも存在します。またmaxSurge maxUnavailableをどの程度の値にすればよいかは環境に依存するため、最適値を求めるまでに試行錯誤が必要になります。

その他
  • トランザクションやセッションが失われる: 新しいPodへと通信先が変わるため、トランザクションやセッションが切断されます。こちらも他のデプロイ方法で共通の問題となる項目になります。

  • アップデートを検証するために外部メトリクスにクエリを投げる機能がない: いつアップデートが完了したかを知ることが難しくなります。

  • アップデートを停止することはできるが、自動的にアップデートを停止してロールバックする方法がない: アップデートに失敗した場合も、自動的にロールバックをすることができず、オペレーションが必要となります。

Blue/Green Deployment

Blue/Green DeploymentはRolling Updateと異なり、Podを一つずつ更新するのではなく、まず新しいバージョンのアプリケーションが起動するための環境を別に作成し、通信の先を新しいバージョンへ一斉に切り替えます。

※画像:Search ITOperations

blue-green

新しいバージョンへの切り替えは一瞬で完了するため、ユーザーが切り替えに気づく前に完了することができます。

またユーザーの利用できるバージョンを一斉に切り替えるため、複数のバージョンが共存している状況については考える必要がなくなります。また新しいバージョンで問題が発生した場合は、通信先を古いバージョンへ戻すことで、素早いロールバックを実現することもできます。

Blue/Green Deploymentによるもう一つの大きな利点は、本番環境でのテストを実行することができるという点です。アプリケーションの稼働する環境を事前に用意するため、新しいアプリケーションに対してのテストを、本番環境で実施することができます。また、それでも新しいバージョンで問題が発生した場合も、古いバージョンに切り替えることで対応することができます。

一方でBlue/Green Deploymentにも問題はあります。

  • アプリケーションを起動するための2倍のスペックが必要: 切り替え時には新しいバージョンの動作環境を用意するため、アプリケーションを起動するのに必要なスペックの2倍を用意する必要があります。

  • N-1 data compatibilityへの対応が必要: 異なるバージョンが同時に起動する状況は防ぐことができますが、バージョン間でのデータの互換性は担保する必要があります。

  • トランザクションやセッションが失われる

Canary Deployment

Canary Deploymentは、新しいバージョンの環境を一部のユーザーだけに提供し、問題がなければ他のユーザーに対しても新しいバージョンを提供する方法です。一部のユーザーに対して新しいバージョンを先に提供するという意味ではRolling Updateとも似ているように見えますが、Canary Deploymentでは特定のユーザーをターゲットにしている点で異なります。

※画像:Search ITOperations

canary

Canary Deploymentを採用することで、より早期のフィードバックとバグの特定を行うことができます。特定のユーザーグループからのフィードバックを得ることで、全てのユーザーに新しいバージョンを展開する前に改善をすることができます。何か問題が発生したとしても、その影響範囲をごく一部に限定することができます。

またBlue/Green Deploymentのように、アプリケーションを動作する2倍のキャパシティを持つ環境を用意する必要もなくなります。

Progressive Delivery

Progressive DeliveryはCDの次のステップとして位置づけられた比較的新しい方法です。新しいバージョンのアプリケーションなどを一部のユーザーに向けてデプロイし、全てのユーザー向けに公開する前に、デプロイしたアプリケーションに対する評価を行い、基準を満たさなければロールバックを実行します。従来のデリバリーに「分析」という観点を加えることで、CDパイプラインによるデプロイスピードと、デプロイに伴うリスクの軽減を実現することが期待されます。

Progressive Deliveryは「Release Progression」と「Progressive Delegation」という2つの観点を含んでいます。Release Progressionは、ビジネスとして適したタイミングでエンドユーザーへアプリケーションや機能をリリースすることを意味します。新機能のデプロイ後にスモークテストを実施したり、一部ユーザーからの利用状況などをベースにして評価を行い、基準を下回る場合は自動的にロールバックを行います。Progressive Delegationは、機能のリリースに関する制御権を、それがビジネスに対してもたらす結果に責任を持つ者へと委任することを意味します。エンジニア以外のチームに機能リリースの制御権を委任し、ビジネス的により適切なタイミングで新機能をリリースすることを可能にします。

Progressive Deliveryにより、リリースへのリスクを軽減することが可能になります。一部のユーザーにのみ先行して機能を提供し、さらに問題があれば以前のバージョンへと切り戻すことで、リリース時に生じる不安感を軽減し、より迅速にデプロイを行うことにつながります。

Argo Rolloutsとは

Argo RolloutsはBlue/Green Deployment、Canary Release、Progressive Deliveryなどのデプロイメント方式をKubernetes上で実現するために開発されたプロダクトになります。Argo Rolloutsでは、以下のような機能を提供し、ArgoCDだけでは実現が難しいデプロイプロセスも実現することができます。

Argo Rolloutsの機能

Blue/Green Deployment・Canary Deploymentの利用

Argo RolloutsはBlue/Green DeploymentやCanary Releaseなどのリリース方式を提供します。Argo RolloutsはRolloutというCRDを利用し、spec.strategyにリリース方式やその内容を定義することで、Rolling Updateよりも高度なデプロイ・リリースを実現します。

spec.strategy.blueGreenを利用する場合、そのRolloutリソースと紐づけるServiceを指定する必要があります。コンテナイメージタグの変更などによってRolloutマニフェストが更新される、と新しいReplicaSetが作成されます。ReplicaSetが正常にデプロイされHealthyとなると、古いPodから新しいPodへ、Serviceからのトラフィック転送先が変更されます。

spec.strategy.canaryを利用する場合、Rolloutにリリースのステップを定義する必要があります。例えばsetWeightではCanary側へ送るトラフィック流量を指定することができます。またpauseを利用すると、条件を満たす、あるいは管理者がステップを進行するよう操作するまで、リリースステップを一時的に中断することができます。

※参考ドキュメント:

自動ロールバック

Argo RolloutsにはAnalysisTemplate AnalysisRunなどのCRDが存在します。これらはRollout中に利用するAnalysisを指定し、その結果によってデプロイ・リリースを進行したり、中断して自動的にロールバックすることを可能にします。

例えばBlue/Green Deploymentではspec.strategy.blueGreen.postPromotionAnalysisという項目で、あらかじめ作成したAnalysisを指定し、その結果に問題がある場合は前のバージョンにロールバックをするよう定義することが可能となります。

※参考ドキュメント:

トラフィックコントロール

Argo Rolloutsは、以下の様なService Meshリソースを操作することで、高度なトラフィックコントロールを実現します。現在対応しているリソースは以下の通りです。

  • Istio
  • Nginx Ingress Controller
  • AWS ALB Ingress Controller
  • Service Mesh Interface (SMI)

※参考ドキュメント:

Progressive Deliveryを実現する分析機能

「自動ロールバック」の項でも少し触れましたが、Argo RolloutsはRolloutに加え、Progressive Deliveryを実現するうえで必要な「分析」に関するCRDを複数備えています。

  • AnalysisTemplate: どのような分析を行うかを定義するテンプレート。Namespace単位で利用することが可能で、利用するメトリクスや実行頻度、成功・失敗を決定する値などを指定します。
  • ClusterAnalysisTemplate: AnalysisTemplateと同じような役割を担いますが、クラスター全体を対象にします。
  • AnalysisRun: AnalysisTemplateを実行するために利用する、KubernetesJobリソースのようなもの。AnalysisRunの結果が、Rolloutのアップデートを継続するか中断するかなどを決定します。
  • Experiment: 分析を行うため、1つ以上のReplicaSetを実行します。

上記CRDはRollout中に指定し、アップデートの過程で実行したり、常時実行するよう設定することも可能です。またBlue/Green Deploymentでは、デプロイ後の切り替え前・切り替え後にAnalysisTemplateを指定することなどもできます。

Analysisに利用できるメトリクスとしては、KubernetesJobWavefrontメトリクスなどがあります。

※参考ドキュメント:

その他

Argo Rolloutsの利用方法

ここからは実際にArgo Rolloutsを利用してみます。今回はArgo Rolloutsドキュメントで紹介されている例を試してみます。

構築環境

今回のKubernetes環境は以下の通りです。

Argo Rolloutsのデプロイ

まずはArgo Rolloutsをデプロイします。

# Argo Rolloutsデプロイ
$ kubectl get ns
NAME              STATUS   AGE
default           Active   21h
kube-node-lease   Active   21h
kube-public       Active   21h
kube-system       Active   21h

$ kubectl create namespace argo-rollouts
namespace/argo-rollouts created

$ kubectl get ns
NAME              STATUS   AGE
argo-rollouts     Active   3s
default           Active   21h
kube-node-lease   Active   21h
kube-public       Active   21h
kube-system       Active   21h

$ kubectl apply -n argo-rollouts -f https://raw.githubusercontent.com/argoproj/argo-rollouts/stable/manifests/install.yaml
customresourcedefinition.apiextensions.k8s.io/analysisruns.argoproj.io created
customresourcedefinition.apiextensions.k8s.io/analysistemplates.argoproj.io created
customresourcedefinition.apiextensions.k8s.io/clusteranalysistemplates.argoproj.io created
customresourcedefinition.apiextensions.k8s.io/experiments.argoproj.io created
customresourcedefinition.apiextensions.k8s.io/rollouts.argoproj.io created
serviceaccount/argo-rollouts created
role.rbac.authorization.k8s.io/argo-rollouts-role created
clusterrole.rbac.authorization.k8s.io/argo-rollouts-aggregate-to-admin created
clusterrole.rbac.authorization.k8s.io/argo-rollouts-aggregate-to-edit created
clusterrole.rbac.authorization.k8s.io/argo-rollouts-aggregate-to-view created
clusterrole.rbac.authorization.k8s.io/argo-rollouts-clusterrole created
rolebinding.rbac.authorization.k8s.io/argo-rollouts-role-binding created
clusterrolebinding.rbac.authorization.k8s.io/argo-rollouts-clusterrolebinding created
service/argo-rollouts-metrics created
deployment.apps/argo-rollouts created


# デプロイ後の確認
$ kubectl get crd
NAME                                         CREATED AT
analysisruns.argoproj.io                     2020-10-10T01:51:57Z
analysistemplates.argoproj.io                2020-10-10T01:51:57Z
clusteranalysistemplates.argoproj.io         2020-10-10T01:51:57Z
eniconfigs.crd.k8s.amazonaws.com             2020-10-09T04:46:45Z
experiments.argoproj.io                      2020-10-10T01:51:57Z
rollouts.argoproj.io                         2020-10-10T01:51:58Z
securitygrouppolicies.vpcresources.k8s.aws   2020-10-09T04:46:47Z

$ kubectl get sa -n argo-rollouts
NAME            SECRETS   AGE
argo-rollouts   1         31s
default         1         48s

$ kubectl get role -n argo-rollouts
NAME                 AGE
argo-rollouts-role   53s

$ kubectl get rolebinding -n argo-rollouts
NAME                         AGE
argo-rollouts-role-binding   74s

$ kubectl get clusterrole
NAME                                                                   AGE
admin                                                                  21h
argo-rollouts-aggregate-to-admin                                       88s★
argo-rollouts-aggregate-to-edit                                        88s★
argo-rollouts-aggregate-to-view                                        87s★
argo-rollouts-clusterrole                                              87s★
aws-node                                                               21h
cluster-admin                                                          21h
edit                                                                   21h
eks:fargate-manager                                                    21h
eks:node-bootstrapper                                                  21h
eks:node-manager                                                       21h
eks:podsecuritypolicy:privileged                                       21h
system:aggregate-to-admin                                              21h
system:aggregate-to-edit                                               21h
system:aggregate-to-view                                               21h
system:auth-delegator                                                  21h
system:basic-user                                                      21h
system:certificates.k8s.io:certificatesigningrequests:nodeclient       21h
system:certificates.k8s.io:certificatesigningrequests:selfnodeclient   21h
system:controller:attachdetach-controller                              21h
system:controller:certificate-controller                               21h
system:controller:clusterrole-aggregation-controller                   21h
system:controller:cronjob-controller                                   21h
system:controller:daemon-set-controller                                21h
system:controller:deployment-controller                                21h
system:controller:disruption-controller                                21h
system:controller:endpoint-controller                                  21h
system:controller:expand-controller                                    21h
system:controller:generic-garbage-collector                            21h
system:controller:horizontal-pod-autoscaler                            21h
system:controller:job-controller                                       21h
system:controller:namespace-controller                                 21h
system:controller:node-controller                                      21h
system:controller:persistent-volume-binder                             21h
system:controller:pod-garbage-collector                                21h
system:controller:pv-protection-controller                             21h
system:controller:pvc-protection-controller                            21h
system:controller:replicaset-controller                                21h
system:controller:replication-controller                               21h
system:controller:resourcequota-controller                             21h
system:controller:route-controller                                     21h
system:controller:service-account-controller                           21h
system:controller:service-controller                                   21h
system:controller:statefulset-controller                               21h
system:controller:ttl-controller                                       21h
system:coredns                                                         21h
system:discovery                                                       21h
system:heapster                                                        21h
system:kube-aggregator                                                 21h
system:kube-controller-manager                                         21h
system:kube-dns                                                        21h
system:kube-scheduler                                                  21h
system:kubelet-api-admin                                               21h
system:node                                                            21h
system:node-bootstrapper                                               21h
system:node-problem-detector                                           21h
system:node-proxier                                                    21h
system:persistent-volume-provisioner                                   21h
system:public-info-viewer                                              21h
system:volume-scheduler                                                21h
view                                                                   21h
vpc-resource-controller-role                                           21h

$ kubectl get clusterrolebinding
NAME                                                   AGE
argo-rollouts-clusterrolebinding                       96s★
aws-node                                               21h
cluster-admin                                          21h
eks:fargate-manager                                    21h
eks:kube-proxy                                         21h
eks:kube-proxy-fargate                                 21h
eks:kube-proxy-windows                                 21h
eks:node-bootstrapper                                  21h
eks:node-manager                                       21h
eks:podsecuritypolicy:authenticated                    21h
system:basic-user                                      21h
system:controller:attachdetach-controller              21h
system:controller:certificate-controller               21h
system:controller:clusterrole-aggregation-controller   21h
system:controller:cronjob-controller                   21h
system:controller:daemon-set-controller                21h
system:controller:deployment-controller                21h
system:controller:disruption-controller                21h
system:controller:endpoint-controller                  21h
system:controller:expand-controller                    21h
system:controller:generic-garbage-collector            21h
system:controller:horizontal-pod-autoscaler            21h
system:controller:job-controller                       21h
system:controller:namespace-controller                 21h
system:controller:node-controller                      21h
system:controller:persistent-volume-binder             21h
system:controller:pod-garbage-collector                21h
system:controller:pv-protection-controller             21h
system:controller:pvc-protection-controller            21h
system:controller:replicaset-controller                21h
system:controller:replication-controller               21h
system:controller:resourcequota-controller             21h
system:controller:route-controller                     21h
system:controller:service-account-controller           21h
system:controller:service-controller                   21h
system:controller:statefulset-controller               21h
system:controller:ttl-controller                       21h
system:coredns                                         21h
system:discovery                                       21h
system:kube-controller-manager                         21h
system:kube-dns                                        21h
system:kube-scheduler                                  21h
system:node                                            21h
system:node-proxier                                    21h
system:public-info-viewer                              21h
system:volume-scheduler                                21h
vpc-resource-controller-rolebinding                    21h

$ kubectl get all -n argo-rollouts
NAME                                READY   STATUS    RESTARTS   AGE
pod/argo-rollouts-bf4f66974-mdwpq   1/1     Running   0          2m6s

NAME                            TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
service/argo-rollouts-metrics   ClusterIP   10.100.214.129   <none>        8090/TCP   2m6s

NAME                            READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/argo-rollouts   1/1     1            1           2m6s

NAME                                      DESIRED   CURRENT   READY   AGE
replicaset.apps/argo-rollouts-bf4f66974   1         1         1       2m6s

Argo Rollouts Pluginのインストール

続いてArgo RolloutsのPluginをインストールします。

$ curl -LO https://github.com/argoproj/argo-rollouts/releases/latest/download/kubectl-argo-rollouts-linux-amd64
$ chmod +x ./kubectl-argo-rollouts-linux-amd64
$ sudo mv ./kubectl-argo-rollouts-linux-amd64 /usr/local/bin/kubectl-argo-rollouts

# インストール後の確認
$ kubectl argo rollouts version
kubectl-argo-rollouts: v0.9.1+3e25fa3
  BuildDate: 2020-09-28T21:50:53Z
  GitCommit: 3e25fa3dfc1788a4a00222d8cd020971d77d72a8
  GitTreeState: clean
  GoVersion: go1.13.1
  Compiler: gc
  Platform: linux/amd64

Rolloutのデプロイ

次にテスト用のRolloutリソースを作成します。今回利用するのは、以下のようにrollout-demoというコンテナイメージを利用したものです。rollout-demoイメージは、複数の「色」をタグに利用することができ(yellow / green / redなど)、デプロイ後のロールアウトは、イメージのタグを変更することで実行します。

apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: rollouts-demo
spec:
  replicas: 5
  strategy:
    canary:
      steps:
      - setWeight: 20
      - pause: {}
      - setWeight: 40
      - pause: {duration: 10}
      - setWeight: 60
      - pause: {duration: 10}
      - setWeight: 80
      - pause: {duration: 10}
  revisionHistoryLimit: 2
  selector:
    matchLabels:
      app: rollouts-demo
  template:
    metadata:
      labels:
        app: rollouts-demo
    spec:
      containers:
      - name: rollouts-demo
        image: argoproj/rollouts-demo:blue
        ports:
        - name: http
          containerPort: 8080
          protocol: TCP
        resources:
          requests:
            memory: 32Mi
            cpu: 5m

上記ファイルについて、少しだけ追記します。

  • Rollout: Rolloutリソースの定義は、KubernetesDeploymentと似たような構成となっており、既にKubernetesを利用している方には使いやすいものかと思います。
  • spec.strategy.canary.steps: 今回のRolloutではCanary Deploymentを利用します。またspec.strategy.canary.stepsでは、各ステップのトラフィック流入量 (setWeight)と一時停止 (pause)の処理を指定しています。

なお、Traffic Managementを利用しない場合、各ステップで指定したsetWeightの値に従って、新しいPodが作成されます。今回はspec.replicas5に設定していますが、最初のステップにあるsetWeight: 20が実行されると、新しいReplicaSetのほうにPodが「1つ」作成され、古いReplicaSetのほうのPodが「1つ」削除されます。以降setWeightの数が増加するに従って、新旧のReplicaSetでのPod数が増減します。spec.replicasをコントロールせずにトラフィックを制御してい場合は、Traffic Managementの機能を利用する必要があります。

# GitHubリポジトリのクローン
$ git clone https://github.com/argoproj/argo-rollouts.git
$ cd argo-rollouts/docs/getting-started/basic/


# Rolloutリソースのデプロイ
$ kubectl apply -f rollout.yaml
rollout.argoproj.io/rollouts-demo created

$ kubectl apply -f service.yaml
service/rollouts-demo created


# デプロイ後の確認
$ kubectl get pods
NAME                             READY   STATUS    RESTARTS   AGE
rollouts-demo-7bf84f9696-57qs6   1/1     Running   0          14s
rollouts-demo-7bf84f9696-hv5hz   1/1     Running   0          14s
rollouts-demo-7bf84f9696-vtz2m   1/1     Running   0          14s
rollouts-demo-7bf84f9696-wbwrk   1/1     Running   0          14s
rollouts-demo-7bf84f9696-xp6dt   1/1     Running   0          14s

$ kubectl get svc
NAME            TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
kubernetes      ClusterIP   10.100.0.1      <none>        443/TCP   35h
rollouts-demo   ClusterIP   10.100.36.201   <none>        80/TCP    11s

$ kubectl get rollout
NAME            DESIRED   CURRENT   UP-TO-DATE   AVAILABLE
rollouts-demo   5         5         5            5

$ kubectl argo rollouts get rollout rollouts-demo
Name:            rollouts-demo
Namespace:       default
Status:          ✔ Healthy
Strategy:        Canary
  Step:          8/8
  SetWeight:     100
  ActualWeight:  100
Images:          argoproj/rollouts-demo:blue (stable)
Replicas:
  Desired:       5
  Current:       5
  Updated:       5
  Ready:         5
  Available:     5

NAME                                       KIND        STATUS     AGE  INFO
⟳ rollouts-demo                            Rollout     ✔ Healthy  65s
└──# revision:1
   └──⧉ rollouts-demo-7bf84f9696           ReplicaSet  ✔ Healthy  65s  stable
      ├──□ rollouts-demo-7bf84f9696-57qs6  Pod         ✔ Running  65s  ready:1/1
      ├──□ rollouts-demo-7bf84f9696-hv5hz  Pod         ✔ Running  65s  ready:1/1
      ├──□ rollouts-demo-7bf84f9696-vtz2m  Pod         ✔ Running  65s  ready:1/1
      ├──□ rollouts-demo-7bf84f9696-wbwrk  Pod         ✔ Running  65s  ready:1/1
      └──□ rollouts-demo-7bf84f9696-xp6dt  Pod         ✔ Running  65s  ready:1/1

※参考ドキュメント:

Rolloutのアップデート

次にデプロイしたRolloutで利用するコンテナイメージを変更し、アップデートを行います。Rolloutspec.templateに変更が走ったときにそれを検知し、アップデートを開始します。ここではkubectl argo rollouts set imageコマンドを利用し、コンテナイメージを変更します。

# マニフェストファイル更新の前に別のターミナルを開き、実行しておく
$ kubectl argo rollouts get rollout rollouts-demo --watch

# イメージタグの変更
$ kubectl argo rollouts set image rollouts-demo rollouts-demo=argoproj/rollouts-demo:yellow
rollout "rollouts-demo" image updated

コンテナイメージを変更すると、別ウィンドウでは以下のように表示内容が変化し、アップデートの進行状況を確認することができます。今回のspec.strategy.canary.stepsでは、まず20%

# ロールアウトの経過を確認する
## 変更直後
Name:            rollouts-demo
Namespace:       default
Status:          ✔ Healthy
Strategy:        Canary
  Step:          8/8
  SetWeight:     100
  ActualWeight:  100
Images:          argoproj/rollouts-demo:blue (stable)
Replicas:
  Desired:       5
  Current:       5
  Updated:       5
  Ready:         5
  Available:     5

NAME                                       KIND        STATUS     AGE   INFO
⟳ rollouts-demo                            Rollout     ✔ Healthy  2m3s
└──# revision:1
   └──⧉ rollouts-demo-7bf84f9696           ReplicaSet  ✔ Healthy  2m3s  stable
      ├──□ rollouts-demo-7bf84f9696-57qs6  Pod         ✔ Running  2m3s  ready:1/1
      ├──□ rollouts-demo-7bf84f9696-hv5hz  Pod         ✔ Running  2m3s  ready:1/1
      ├──□ rollouts-demo-7bf84f9696-vtz2m  Pod         ✔ Running  2m3s  ready:1/1
      ├──□ rollouts-demo-7bf84f9696-wbwrk  Pod         ✔ Running  2m3s  ready:1/1
      └──□ rollouts-demo-7bf84f9696-xp6dt  Pod         ✔ Running  2m3s  ready:1/1


## ロールアウトの開始 (StatusがProgressingになる)
Name:            rollouts-demo
Namespace:       default
Status:          ◌ Progressing
Strategy:        Canary
  Step:          0/8
  SetWeight:     20
  ActualWeight:  0
Images:          argoproj/rollouts-demo:blue (stable)
                 argoproj/rollouts-demo:yellow (canary)
Replicas:
  Desired:       5
  Current:       6
  Updated:       1
  Ready:         5
  Available:     5

NAME                                       KIND        STATUS         AGE   INFO
⟳ rollouts-demo                            Rollout     ◌ Progressing  2m5s
├──# revision:2
│  └──⧉ rollouts-demo-789746c88d           ReplicaSet  ◌ Progressing  1s    canary
│     └──□ rollouts-demo-789746c88d-gjgbl  Pod         ✔ Running      1s    ready:1/1
└──# revision:1
   └──⧉ rollouts-demo-7bf84f9696           ReplicaSet  ✔ Healthy      2m5s  stable
      ├──□ rollouts-demo-7bf84f9696-57qs6  Pod         ✔ Running      2m5s  ready:1/1
      ├──□ rollouts-demo-7bf84f9696-hv5hz  Pod         ✔ Running      2m5s  ready:1/1
      ├──□ rollouts-demo-7bf84f9696-vtz2m  Pod         ✔ Running      2m5s  ready:1/1
      ├──□ rollouts-demo-7bf84f9696-wbwrk  Pod         ✔ Running      2m5s  ready:1/1
      └──□ rollouts-demo-7bf84f9696-xp6dt  Pod         ✔ Running      2m5s  ready:1/1


## 1つ目のステップ (setWeight: 20)が完了し、Status=Pausedになる
Name:            rollouts-demo
Namespace:       default
Status:          ॥ Paused
Strategy:        Canary
  Step:          1/8
  SetWeight:     20
  ActualWeight:  20
Images:          argoproj/rollouts-demo:blue (stable)
                 argoproj/rollouts-demo:yellow (canary)
Replicas:
  Desired:       5
  Current:       5
  Updated:       1
  Ready:         5
  Available:     5

NAME                                       KIND        STATUS     AGE    INFO
⟳ rollouts-demo                            Rollout     ॥ Paused   2m19s
├──# revision:2
│  └──⧉ rollouts-demo-789746c88d           ReplicaSet  ✔ Healthy  15s    canary
│     └──□ rollouts-demo-789746c88d-gjgbl  Pod         ✔ Running  15s    ready:1/1
└──# revision:1
   └──⧉ rollouts-demo-7bf84f9696           ReplicaSet  ✔ Healthy  2m19s  stable
      ├──□ rollouts-demo-7bf84f9696-57qs6  Pod         ✔ Running  2m19s  ready:1/1
      ├──□ rollouts-demo-7bf84f9696-hv5hz  Pod         ✔ Running  2m19s  ready:1/1
      ├──□ rollouts-demo-7bf84f9696-vtz2m  Pod         ✔ Running  2m19s  ready:1/1
      └──□ rollouts-demo-7bf84f9696-wbwrk  Pod         ✔ Running  2m19s  ready:1/1

Rolloutの進行

テスト用のRolloutの定義を見ると、spec.strategy.canary.stepspauseが定義されています。上記までの手順では、2つ目のステップの時点でpauseがセットされており、またその終了条件が記載されていません。これを以降のステップへ進行するため、kubectl argo rolloutsコマンドを実行します。

# Pauseを完了する前に、別のターミナルを開き、実行しておく
$ kubectl argo rollouts get rollout rollouts-demo --watch


# Pause状態からの進行を行う
$ kubectl argo rollouts promote rollouts-demo
rollout 'rollouts-demo' promoted


# ロールアウトの経過を確認する
## 変更直後
Name:            rollouts-demo
Namespace:       default
Status:          ॥ Paused
Strategy:        Canary
  Step:          1/8
  SetWeight:     20
  ActualWeight:  20
Images:          argoproj/rollouts-demo:blue (stable)
                 argoproj/rollouts-demo:yellow (canary)
Replicas:
  Desired:       5
  Current:       5
  Updated:       1
  Ready:         5
  Available:     5

NAME                                       KIND        STATUS     AGE    INFO
⟳ rollouts-demo                            Rollout     ॥ Paused   4m23s
├──# revision:2
│  └──⧉ rollouts-demo-789746c88d           ReplicaSet  ✔ Healthy  2m19s  canary
│     └──□ rollouts-demo-789746c88d-gjgbl  Pod         ✔ Running  2m19s  ready:1/1
└──# revision:1
   └──⧉ rollouts-demo-7bf84f9696           ReplicaSet  ✔ Healthy  4m23s  stable
      ├──□ rollouts-demo-7bf84f9696-57qs6  Pod         ✔ Running  4m23s  ready:1/1
      ├──□ rollouts-demo-7bf84f9696-hv5hz  Pod         ✔ Running  4m23s  ready:1/1
      ├──□ rollouts-demo-7bf84f9696-vtz2m  Pod         ✔ Running  4m23s  ready:1/1
      └──□ rollouts-demo-7bf84f9696-wbwrk  Pod         ✔ Running  4m23s  ready:1/1


## 進行開始
### StatusがProgressingになる
Name:            rollouts-demo
Namespace:       default
Status:          ◌ Progressing
Strategy:        Canary
  Step:          2/8
  SetWeight:     40
  ActualWeight:  20
Images:          argoproj/rollouts-demo:blue (stable)
                 argoproj/rollouts-demo:yellow (canary)
Replicas:
  Desired:       5
  Current:       5
  Updated:       1
  Ready:         5
  Available:     5

NAME                                       KIND        STATUS         AGE    INFO
⟳ rollouts-demo                            Rollout     ◌ Progressing  4m23s
├──# revision:2
│  └──⧉ rollouts-demo-789746c88d           ReplicaSet  ◌ Progressing  2m19s  canary
│     ├──□ rollouts-demo-789746c88d-gjgbl  Pod         ✔ Running      2m19s  ready:1/1
│     └──□ rollouts-demo-789746c88d-nnv2n  Pod         ◌ Pending      0s     ready:0/1
└──# revision:1
   └──⧉ rollouts-demo-7bf84f9696           ReplicaSet  ✔ Healthy      4m23s  stable
      ├──□ rollouts-demo-7bf84f9696-57qs6  Pod         ✔ Running      4m23s  ready:1/1
      ├──□ rollouts-demo-7bf84f9696-hv5hz  Pod         ✔ Running      4m23s  ready:1/1
      ├──□ rollouts-demo-7bf84f9696-vtz2m  Pod         ✔ Running      4m23s  ready:1/1
      └──□ rollouts-demo-7bf84f9696-wbwrk  Pod         ✔ Running      4m23s  ready:1/1


### Pauseが終了し、次のステップに移行 (新しいPodが作成され始める)

Name:            rollouts-demo
Namespace:       default
Status:          ◌ Progressing
Strategy:        Canary
  Step:          2/8
  SetWeight:     40
  ActualWeight:  20
Images:          argoproj/rollouts-demo:blue (stable)
                 argoproj/rollouts-demo:yellow (canary)
Replicas:
  Desired:       5
  Current:       6
  Updated:       2
  Ready:         5
  Available:     5

NAME                                       KIND        STATUS               AGE    INFO
⟳ rollouts-demo                            Rollout     ◌ Progressing        4m24s
├──# revision:2
│  └──⧉ rollouts-demo-789746c88d           ReplicaSet  ◌ Progressing        2m20s  canary
│     ├──□ rollouts-demo-789746c88d-gjgbl  Pod         ✔ Running            2m20s  ready:1/1
│     └──□ rollouts-demo-789746c88d-nnv2n  Pod         ◌ ContainerCreating  1s     ready:0/1
└──# revision:1
   └──⧉ rollouts-demo-7bf84f9696           ReplicaSet  ✔ Healthy            4m24s  stable
      ├──□ rollouts-demo-7bf84f9696-57qs6  Pod         ✔ Running            4m24s  ready:1/1
      ├──□ rollouts-demo-7bf84f9696-hv5hz  Pod         ✔ Running            4m24s  ready:1/1
      ├──□ rollouts-demo-7bf84f9696-vtz2m  Pod         ✔ Running            4m24s  ready:1/1
      └──□ rollouts-demo-7bf84f9696-wbwrk  Pod         ✔ Running            4m24s  ready:1/1

### 新しいPodが作成される

Name:            rollouts-demo
Namespace:       default
Status:          ◌ Progressing
Strategy:        Canary
  Step:          2/8
  SetWeight:     40
  ActualWeight:  20
Images:          argoproj/rollouts-demo:blue (stable)
                 argoproj/rollouts-demo:yellow (canary)
Replicas:
  Desired:       5
  Current:       6
  Updated:       2
  Ready:         5
  Available:     5

NAME                                       KIND        STATUS         AGE    INFO
⟳ rollouts-demo                            Rollout     ◌ Progressing  4m24s
├──# revision:2
│  └──⧉ rollouts-demo-789746c88d           ReplicaSet  ◌ Progressing  2m20s  canary
│     ├──□ rollouts-demo-789746c88d-gjgbl  Pod         ✔ Running      2m20s  ready:1/1
│     └──□ rollouts-demo-789746c88d-nnv2n  Pod         ✔ Running      1s     ready:1/1
└──# revision:1
   └──⧉ rollouts-demo-7bf84f9696           ReplicaSet  ✔ Healthy      4m24s  stable
      ├──□ rollouts-demo-7bf84f9696-57qs6  Pod         ✔ Running      4m24s  ready:1/1
      ├──□ rollouts-demo-7bf84f9696-hv5hz  Pod         ✔ Running      4m24s  ready:1/1
      ├──□ rollouts-demo-7bf84f9696-vtz2m  Pod         ✔ Running      4m24s  ready:1/1
      └──□ rollouts-demo-7bf84f9696-wbwrk  Pod         ✔ Running      4m24s  ready:1/1


## 以降のステップがが順次進行される
Name:            rollouts-demo
Namespace:       default
Status:          ◌ Progressing
Strategy:        Canary
  Step:          4/8
  SetWeight:     60
  ActualWeight:  40
Images:          argoproj/rollouts-demo:blue (stable)
                 argoproj/rollouts-demo:yellow (canary)
Replicas:
  Desired:       5
  Current:       6
  Updated:       3
  Ready:         5
  Available:     5

NAME                                       KIND        STATUS               AGE    INFO
⟳ rollouts-demo                            Rollout     ◌ Progressing        4m34s
├──# revision:2
│  └──⧉ rollouts-demo-789746c88d           ReplicaSet  ◌ Progressing        2m30s  canary
│     ├──□ rollouts-demo-789746c88d-gjgbl  Pod         ✔ Running            2m30s  ready:1/1
│     ├──□ rollouts-demo-789746c88d-nnv2n  Pod         ✔ Running            11s    ready:1/1
│     └──□ rollouts-demo-789746c88d-vghjp  Pod         ◌ ContainerCreating  0s     ready:0/1
└──# revision:1
   └──⧉ rollouts-demo-7bf84f9696           ReplicaSet  ✔ Healthy            4m34s  stable
      ├──□ rollouts-demo-7bf84f9696-57qs6  Pod         ✔ Running            4m34s  ready:1/1
      ├──□ rollouts-demo-7bf84f9696-hv5hz  Pod         ✔ Running            4m34s  ready:1/1
      ├──□ rollouts-demo-7bf84f9696-vtz2m  Pod         ✔ Running            4m34s  ready:1/1
      └──□ rollouts-demo-7bf84f9696-wbwrk  Pod         ◌ Terminating        4m34s  ready:1/1


## 完了後
Name:            rollouts-demo
Namespace:       default
Status:          ✔ Healthy
Strategy:        Canary
  Step:          8/8
  SetWeight:     100
  ActualWeight:  100
Images:          argoproj/rollouts-demo:yellow (stable)
Replicas:
  Desired:       5
  Current:       5
  Updated:       5
  Ready:         5
  Available:     5

NAME                                       KIND        STATUS         AGE    INFO
⟳ rollouts-demo                            Rollout     ✔ Healthy      5m17s
├──# revision:2
│  └──⧉ rollouts-demo-789746c88d           ReplicaSet  ✔ Healthy      3m13s  stable
│     ├──□ rollouts-demo-789746c88d-gjgbl  Pod         ✔ Running      3m13s  ready:1/1
│     ├──□ rollouts-demo-789746c88d-nnv2n  Pod         ✔ Running      54s    ready:1/1
│     ├──□ rollouts-demo-789746c88d-vghjp  Pod         ✔ Running      43s    ready:1/1
│     ├──□ rollouts-demo-789746c88d-jsjxg  Pod         ✔ Running      32s    ready:1/1
│     └──□ rollouts-demo-789746c88d-8plpp  Pod         ✔ Running      21s    ready:1/1
└──# revision:1
   └──⧉ rollouts-demo-7bf84f9696           ReplicaSet  • ScaledDown   5m17s
      └──□ rollouts-demo-7bf84f9696-57qs6  Pod         ◌ Terminating  5m17s  ready:0/1

また完了後のPod・Rolloutなどのリソースは以下のようになっています。

$ kubectl get pods
NAME                             READY   STATUS    RESTARTS   AGE
rollouts-demo-789746c88d-8plpp   1/1     Running   0          2m41s
rollouts-demo-789746c88d-gjgbl   1/1     Running   0          5m33s
rollouts-demo-789746c88d-jsjxg   1/1     Running   0          2m52s
rollouts-demo-789746c88d-nnv2n   1/1     Running   0          3m14s
rollouts-demo-789746c88d-vghjp   1/1     Running   0          3m3s


$ kubectl get rollout
NAME            DESIRED   CURRENT   UP-TO-DATE   AVAILABLE
rollouts-demo   5         5         5            5


$ kubectl describe rollout rollouts-demo
Name:         rollouts-demo
Namespace:    default
Labels:       <none>
Annotations:  rollout.argoproj.io/revision: 2
API Version:  argoproj.io/v1alpha1
Kind:         Rollout
Metadata:
  Creation Timestamp:  2020-10-10T16:00:06Z
  Generation:          45
  Resource Version:    393821
  Self Link:           /apis/argoproj.io/v1alpha1/namespaces/default/rollouts/rollouts-demo
  UID:                 77fe4f75-920b-4286-957f-3d4dd22a6639
Spec:
  Paused:                  false
  Replicas:                5
  Revision History Limit:  2
  Selector:
    Match Labels:
      App:  rollouts-demo
  Strategy:
    Canary:
      Steps:
        Set Weight:  20
        Pause:
        Set Weight:  40
        Pause:
          Duration:  10
        Set Weight:  60
        Pause:
          Duration:  10
        Set Weight:  80
        Pause:
          Duration:  10
  Template:
    Metadata:
      Creation Timestamp:  <nil>
      Labels:
        App:  rollouts-demo
    Spec:
      Containers:
        Image:  argoproj/rollouts-demo:yellow
        Name:   rollouts-demo
        Ports:
          Container Port:  8080
          Name:            http
          Protocol:        TCP
        Resources:
          Requests:
            Cpu:     5m
            Memory:  32Mi
Status:
  HPA Replicas:        5
  Available Replicas:  5
  Blue Green:
  Canary:
    Stable RS:  789746c88d
  Conditions:
    Last Transition Time:  2020-10-10T16:00:09Z
    Last Update Time:      2020-10-10T16:00:09Z
    Message:               Rollout has minimum availability
    Reason:                AvailableReason
    Status:                True
    Type:                  Available
    Last Transition Time:  2020-10-10T16:05:02Z
    Last Update Time:      2020-10-10T16:05:03Z
    Message:               ReplicaSet "rollouts-demo-789746c88d" has successfully progressed.
    Reason:                NewReplicaSetAvailable
    Status:                True
    Type:                  Progressing
  Current Pod Hash:        789746c88d
  Current Step Hash:       f64cdc9d
  Current Step Index:      8
  Observed Generation:     fcd8c79f9
  Ready Replicas:          5
  Replicas:                5
  Selector:                app=rollouts-demo
  Stable RS:               789746c88d
  Updated Replicas:        5
Events:
  Type    Reason             Age                  From                 Message
  ----    ------             ----                 ----                 -------
  Normal  ScalingReplicaSet  8m3s                 rollouts-controller  Scaled up replica set rollouts-demo-7bf84f9696 to 5
  Normal  SettingStableRS    8m3s                 rollouts-controller  Setting StableRS to CurrentPodHash: StableRS hash: 7bf84f9696
  Normal  ScalingReplicaSet  5m59s                rollouts-controller  Scaled up replica set rollouts-demo-789746c88d to 1
  Normal  ScalingReplicaSet  5m58s                rollouts-controller  Scaled down replica set rollouts-demo-7bf84f9696 to 4
  Normal  SetStepIndex       5m58s                rollouts-controller  Set Step Index to 1
  Normal  SetStepIndex       3m40s                rollouts-controller  Set Step Index to 2
  Normal  ScalingReplicaSet  3m40s                rollouts-controller  Scaled up replica set rollouts-demo-789746c88d to 2
  Normal  ScalingReplicaSet  3m39s                rollouts-controller  Scaled down replica set rollouts-demo-7bf84f9696 to 3
  Normal  SetStepIndex       3m39s                rollouts-controller  Set Step Index to 3
  Normal  SetStepIndex       3m29s                rollouts-controller  Set Step Index to 4
  Normal  ScalingReplicaSet  3m29s                rollouts-controller  Scaled up replica set rollouts-demo-789746c88d to 3
  Normal  ScalingReplicaSet  3m28s                rollouts-controller  Scaled down replica set rollouts-demo-7bf84f9696 to 2
  Normal  SetStepIndex       3m28s                rollouts-controller  Set Step Index to 5
  Normal  SetStepIndex       3m18s                rollouts-controller  Set Step Index to 6
  Normal  ScalingReplicaSet  3m18s                rollouts-controller  Scaled up replica set rollouts-demo-789746c88d to 4
  Normal  ScalingReplicaSet  3m18s                rollouts-controller  Scaled down replica set rollouts-demo-7bf84f9696 to 1
  Normal  SetStepIndex       3m17s                rollouts-controller  Set Step Index to 7
  Normal  SettingStableRS    3m7s                 rollouts-controller  Completed all steps
  Normal  SetStepIndex       3m7s                 rollouts-controller  Set Step Index to 8
  Normal  ScalingReplicaSet  3m6s (x2 over 3m7s)  rollouts-controller  (combined from similar events): Scaled down replica set rollouts-demo-7bf84f9696 to 0

Rolloutの中断

次に、先ほどPause状態から次ステップへ進めた段階でロールアウトを中断する例を見てみます。先ほどデプロイしたRolloutを引き続き利用し、今度はイメージタグをredに変更します。

# 別のターミナルで実行
$ kubectl argo rollouts get rollout rollouts-demo --watch


# イメージタグの更新
$ kubectl argo rollouts set image rollouts-demo rollouts-demo=argoproj/rollouts-demo:red
rollout "rollouts-demo" image updated


# Pause状態まで待機
Name:            rollouts-demo
Namespace:       default
Status:          ॥ Paused
Strategy:        Canary
  Step:          1/8
  SetWeight:     20
  ActualWeight:  20
Images:          argoproj/rollouts-demo:red (canary)
                 argoproj/rollouts-demo:yellow (stable)
Replicas:
  Desired:       5
  Current:       5
  Updated:       1
  Ready:         5
  Available:     5

NAME                                       KIND        STATUS         AGE    INFO
⟳ rollouts-demo                            Rollout     ॥ Paused       9m11s
├──# revision:3
│  └──⧉ rollouts-demo-6f75f48b7            ReplicaSet  ✔ Healthy      8s     canary
│     └──□ rollouts-demo-6f75f48b7-4pg88   Pod         ✔ Running      8s     ready:1/1
├──# revision:2
│  └──⧉ rollouts-demo-789746c88d           ReplicaSet  ✔ Healthy      7m7s   stable
│     ├──□ rollouts-demo-789746c88d-gjgbl  Pod         ✔ Running      7m7s   ready:1/1
│     ├──□ rollouts-demo-789746c88d-nnv2n  Pod         ✔ Running      4m48s  ready:1/1
│     ├──□ rollouts-demo-789746c88d-vghjp  Pod         ✔ Running      4m37s  ready:1/1
│     ├──□ rollouts-demo-789746c88d-jsjxg  Pod         ✔ Running      4m26s  ready:1/1
│     └──□ rollouts-demo-789746c88d-8plpp  Pod         ◌ Terminating  4m15s  ready:1/1
└──# revision:1
   └──⧉ rollouts-demo-7bf84f9696           ReplicaSet  • ScaledDown   9m11s

Pause状態になったら、今回はkubectl argo rollouts abortコマンドを実行し、ロールアウトを中断します。

# 別のターミナルで実行
$ kubectl argo rollouts get rollout rollouts-demo --watch


# ロールアウトの中断
$ kubectl argo rollouts abort rollouts-demo
rollout 'rollouts-demo' aborted


# ロールアウトの経過を確認
## 中断直後
Name:            rollouts-demo
Namespace:       default
Status:          ॥ Paused
Strategy:        Canary
  Step:          1/8
  SetWeight:     20
  ActualWeight:  20
Images:          argoproj/rollouts-demo:red (canary)
                 argoproj/rollouts-demo:yellow (stable)
Replicas:
  Desired:       5
  Current:       5
  Updated:       1
  Ready:         5
  Available:     5

NAME                                       KIND        STATUS        AGE    INFO
⟳ rollouts-demo                            Rollout     ॥ Paused      10m
├──# revision:3
│  └──⧉ rollouts-demo-6f75f48b7            ReplicaSet  ✔ Healthy     97s    canary
│     └──□ rollouts-demo-6f75f48b7-4pg88   Pod         ✔ Running     97s    ready:1/1
├──# revision:2
│  └──⧉ rollouts-demo-789746c88d           ReplicaSet  ✔ Healthy     8m36s  stable
│     ├──□ rollouts-demo-789746c88d-gjgbl  Pod         ✔ Running     8m36s  ready:1/1
│     ├──□ rollouts-demo-789746c88d-nnv2n  Pod         ✔ Running     6m17s  ready:1/1
│     ├──□ rollouts-demo-789746c88d-vghjp  Pod         ✔ Running     6m6s   ready:1/1
│     └──□ rollouts-demo-789746c88d-jsjxg  Pod         ✔ Running     5m55s  ready:1/1
└──# revision:1
   └──⧉ rollouts-demo-7bf84f9696           ReplicaSet  • ScaledDown  10m


## 中断開始
Name:            rollouts-demo
Namespace:       default
Status:          ✖ Degraded
Strategy:        Canary
  Step:          0/8
  SetWeight:     0
  ActualWeight:  0
Images:          argoproj/rollouts-demo:yellow (stable)
Replicas:
  Desired:       5
  Current:       5
  Updated:       0
  Ready:         4
  Available:     4

NAME                                       KIND        STATUS               AGE    INFO
⟳ rollouts-demo                            Rollout     ✖ Degraded           10m
├──# revision:3
│  └──⧉ rollouts-demo-6f75f48b7            ReplicaSet  • ScaledDown         98s    canary
│     └──□ rollouts-demo-6f75f48b7-4pg88   Pod         ◌ Terminating        98s    ready:1/1
├──# revision:2
│  └──⧉ rollouts-demo-789746c88d           ReplicaSet  ◌ Progressing        8m37s  stable
│     ├──□ rollouts-demo-789746c88d-gjgbl  Pod         ✔ Running            8m37s  ready:1/1
│     ├──□ rollouts-demo-789746c88d-nnv2n  Pod         ✔ Running            6m18s  ready:1/1
│     ├──□ rollouts-demo-789746c88d-vghjp  Pod         ✔ Running            6m7s   ready:1/1
│     ├──□ rollouts-demo-789746c88d-jsjxg  Pod         ✔ Running            5m56s  ready:1/1
│     └──□ rollouts-demo-789746c88d-wqcjc  Pod         ◌ ContainerCreating  0s     ready:0/1
└──# revision:1
   └──⧉ rollouts-demo-7bf84f9696           ReplicaSet  • ScaledDown         10m


## 完了後
Name:            rollouts-demo
Namespace:       default
Status:          ✖ Degraded
Strategy:        Canary
  Step:          0/8
  SetWeight:     0
  ActualWeight:  0
Images:          argoproj/rollouts-demo:yellow (stable)
Replicas:
  Desired:       5
  Current:       5
  Updated:       0
  Ready:         5
  Available:     5

NAME                                       KIND        STATUS        AGE    INFO
⟳ rollouts-demo                            Rollout     ✖ Degraded    10m
├──# revision:3
│  └──⧉ rollouts-demo-6f75f48b7            ReplicaSet  • ScaledDown  111s   canary
├──# revision:2
│  └──⧉ rollouts-demo-789746c88d           ReplicaSet  ✔ Healthy     8m50s  stable
│     ├──□ rollouts-demo-789746c88d-gjgbl  Pod         ✔ Running     8m50s  ready:1/1
│     ├──□ rollouts-demo-789746c88d-nnv2n  Pod         ✔ Running     6m31s  ready:1/1
│     ├──□ rollouts-demo-789746c88d-vghjp  Pod         ✔ Running     6m20s  ready:1/1
│     ├──□ rollouts-demo-789746c88d-jsjxg  Pod         ✔ Running     6m9s   ready:1/1
│     └──□ rollouts-demo-789746c88d-wqcjc  Pod         ✔ Running     13s    ready:1/1
└──# revision:1
   └──⧉ rollouts-demo-7bf84f9696           ReplicaSet  • ScaledDown  10m

上記のようにロールバックを中断すると、StatusはDegradedとなります。これを再びHealthyの状態にするには、正常なイメージタグを指定して再デプロイする必要があります。既にデプロイ済みのバージョンを指定するので、デプロイ自体は高速で完了します。

# 別ターミナルで実行
$ kubectl argo rollouts get rollout rollouts-demo --watch


# 再デプロイ
$ kubectl argo rollouts set image rollouts-demo rollouts-demo=argoproj/rollouts-demo:yellow
rollout "rollouts-demo" image updated


# ロールアウトの経過を確認
## 再デプロイ直後
Name:            rollouts-demo
Namespace:       default
Status:          ✖ Degraded
Strategy:        Canary
  Step:          0/8
  SetWeight:     0
  ActualWeight:  0
Images:          argoproj/rollouts-demo:yellow (stable)
Replicas:
  Desired:       5
  Current:       5
  Updated:       0
  Ready:         5
  Available:     5

NAME                                       KIND        STATUS        AGE    INFO
⟳ rollouts-demo                            Rollout     ✖ Degraded    12m
├──# revision:3
│  └──⧉ rollouts-demo-6f75f48b7            ReplicaSet  • ScaledDown  3m42s  canary
├──# revision:2
│  └──⧉ rollouts-demo-789746c88d           ReplicaSet  ✔ Healthy     10m    stable
│     ├──□ rollouts-demo-789746c88d-gjgbl  Pod         ✔ Running     10m    ready:1/1
│     ├──□ rollouts-demo-789746c88d-nnv2n  Pod         ✔ Running     8m22s  ready:1/1
│     ├──□ rollouts-demo-789746c88d-vghjp  Pod         ✔ Running     8m11s  ready:1/1
│     ├──□ rollouts-demo-789746c88d-jsjxg  Pod         ✔ Running     8m     ready:1/1
│     └──□ rollouts-demo-789746c88d-wqcjc  Pod         ✔ Running     2m4s   ready:1/1
└──# revision:1
   └──⧉ rollouts-demo-7bf84f9696           ReplicaSet  • ScaledDown  12m


## 再デプロイの開始
Name:            rollouts-demo
Namespace:       default
Status:          ✖ Degraded
Strategy:        Canary
  Step:          0/8
  SetWeight:     0
  ActualWeight:  0
Images:          argoproj/rollouts-demo:yellow (stable)
Replicas:
  Desired:       5
  Current:       5
  Updated:       0
  Ready:         5
  Available:     5

NAME                                       KIND        STATUS        AGE    INFO
⟳ rollouts-demo                            Rollout     ✖ Degraded    12m
├──# revision:4
│  └──⧉ rollouts-demo-789746c88d           ReplicaSet  ✔ Healthy     10m    stable
│     ├──□ rollouts-demo-789746c88d-gjgbl  Pod         ✔ Running     10m    ready:1/1
│     ├──□ rollouts-demo-789746c88d-nnv2n  Pod         ✔ Running     8m23s  ready:1/1
│     ├──□ rollouts-demo-789746c88d-vghjp  Pod         ✔ Running     8m12s  ready:1/1
│     ├──□ rollouts-demo-789746c88d-jsjxg  Pod         ✔ Running     8m1s   ready:1/1
│     └──□ rollouts-demo-789746c88d-wqcjc  Pod         ✔ Running     2m5s   ready:1/1
├──# revision:3
│  └──⧉ rollouts-demo-6f75f48b7            ReplicaSet  • ScaledDown  3m43s  canary
└──# revision:1
   └──⧉ rollouts-demo-7bf84f9696           ReplicaSet  • ScaledDown  12m


## 完了後
Name:            rollouts-demo
Namespace:       default
Status:          ✔ Healthy
Strategy:        Canary
  Step:          8/8
  SetWeight:     100
  ActualWeight:  100
Images:          argoproj/rollouts-demo:yellow (stable)
Replicas:
  Desired:       5
  Current:       5
  Updated:       5
  Ready:         5
  Available:     5

NAME                                       KIND        STATUS        AGE    INFO
⟳ rollouts-demo                            Rollout     ✔ Healthy     12m
├──# revision:4
│  └──⧉ rollouts-demo-789746c88d           ReplicaSet  ✔ Healthy     10m    stable
│     ├──□ rollouts-demo-789746c88d-gjgbl  Pod         ✔ Running     10m    ready:1/1
│     ├──□ rollouts-demo-789746c88d-nnv2n  Pod         ✔ Running     8m23s  ready:1/1
│     ├──□ rollouts-demo-789746c88d-vghjp  Pod         ✔ Running     8m12s  ready:1/1
│     ├──□ rollouts-demo-789746c88d-jsjxg  Pod         ✔ Running     8m1s   ready:1/1
│     └──□ rollouts-demo-789746c88d-wqcjc  Pod         ✔ Running     2m5s   ready:1/1
├──# revision:3
│  └──⧉ rollouts-demo-6f75f48b7            ReplicaSet  • ScaledDown  3m43s
└──# revision:1
   └──⧉ rollouts-demo-7bf84f9696           ReplicaSet  • ScaledDown  12m

また完了後のRolloutは以下のようになっています。

$ kubectl describe rollout rollouts-demo
Name:         rollouts-demo
Namespace:    default
Labels:       <none>
Annotations:  rollout.argoproj.io/revision: 4
API Version:  argoproj.io/v1alpha1
Kind:         Rollout
Metadata:
  Creation Timestamp:  2020-10-10T16:00:06Z
  Generation:          63
  Resource Version:    395343
  Self Link:           /apis/argoproj.io/v1alpha1/namespaces/default/rollouts/rollouts-demo
  UID:                 77fe4f75-920b-4286-957f-3d4dd22a6639
Spec:
  Replicas:                5
  Revision History Limit:  2
  Selector:
    Match Labels:
      App:  rollouts-demo
  Strategy:
    Canary:
      Steps:
        Set Weight:  20
        Pause:
        Set Weight:  40
        Pause:
          Duration:  10
        Set Weight:  60
        Pause:
          Duration:  10
        Set Weight:  80
        Pause:
          Duration:  10
  Template:
    Metadata:
      Creation Timestamp:  <nil>
      Labels:
        App:  rollouts-demo
    Spec:
      Containers:
        Image:  argoproj/rollouts-demo:yellow
        Name:   rollouts-demo
        Ports:
          Container Port:  8080
          Name:            http
          Protocol:        TCP
        Resources:
          Requests:
            Cpu:     5m
            Memory:  32Mi
Status:
  HPA Replicas:        5
  Available Replicas:  5
  Blue Green:
  Canary:
    Stable RS:  789746c88d
  Conditions:
    Last Transition Time:  2020-10-10T16:10:48Z
    Last Update Time:      2020-10-10T16:10:48Z
    Message:               Rollout has minimum availability
    Reason:                AvailableReason
    Status:                True
    Type:                  Available
    Last Transition Time:  2020-10-10T16:12:52Z
    Last Update Time:      2020-10-10T16:12:52Z
    Message:               ReplicaSet "rollouts-demo-789746c88d" has successfully progressed.
    Reason:                NewReplicaSetAvailable
    Status:                True
    Type:                  Progressing
  Current Pod Hash:        789746c88d
  Current Step Hash:       f64cdc9d
  Current Step Index:      8
  Observed Generation:     fcd8c79f9
  Ready Replicas:          5
  Replicas:                5
  Selector:                app=rollouts-demo
  Stable RS:               789746c88d
  Updated Replicas:        5
Events:
  Type    Reason             Age                  From                 Message
  ----    ------             ----                 ----                 -------
  Normal  SettingStableRS    15m                  rollouts-controller  Setting StableRS to CurrentPodHash: StableRS hash: 7bf84f9696
  Normal  ScalingReplicaSet  15m                  rollouts-controller  Scaled up replica set rollouts-demo-7bf84f9696 to 5
  Normal  ScalingReplicaSet  13m                  rollouts-controller  Scaled up replica set rollouts-demo-789746c88d to 1
  Normal  ScalingReplicaSet  13m                  rollouts-controller  Scaled down replica set rollouts-demo-7bf84f9696 to 4
  Normal  ScalingReplicaSet  11m                  rollouts-controller  Scaled up replica set rollouts-demo-789746c88d to 2
  Normal  SetStepIndex       11m                  rollouts-controller  Set Step Index to 2
  Normal  ScalingReplicaSet  11m                  rollouts-controller  Scaled down replica set rollouts-demo-7bf84f9696 to 3
  Normal  SetStepIndex       11m                  rollouts-controller  Set Step Index to 3
  Normal  SetStepIndex       10m                  rollouts-controller  Set Step Index to 4
  Normal  ScalingReplicaSet  10m                  rollouts-controller  Scaled up replica set rollouts-demo-789746c88d to 3
  Normal  ScalingReplicaSet  10m                  rollouts-controller  Scaled down replica set rollouts-demo-7bf84f9696 to 2
  Normal  SetStepIndex       10m                  rollouts-controller  Set Step Index to 5
  Normal  SetStepIndex       10m                  rollouts-controller  Set Step Index to 6
  Normal  ScalingReplicaSet  10m                  rollouts-controller  Scaled up replica set rollouts-demo-789746c88d to 4
  Normal  ScalingReplicaSet  10m                  rollouts-controller  Scaled down replica set rollouts-demo-7bf84f9696 to 1
  Normal  SetStepIndex       10m                  rollouts-controller  Set Step Index to 7
  Normal  SettingStableRS    10m                  rollouts-controller  Completed all steps
  Normal  SetStepIndex       10m                  rollouts-controller  Set Step Index to 8
  Normal  SetStepIndex       6m23s (x2 over 13m)  rollouts-controller  Set Step Index to 1
  Normal  ScalingReplicaSet  4m46s (x6 over 10m)  rollouts-controller  (combined from similar events): Scaled down replica set rollouts-demo-6f75f48b7 to 0
  Normal  SkipSteps          2m41s                rollouts-controller  Skipping all steps because the newRS is the stableRS.

ArgoCDに入門する

はじめに

本記事ではGitOps CDツールの一つであるArgoCDの概要と利用方法について紹介いたします。

ArgoCDとは

ArgoCDはKubernetesクラスター向けのContinuous Deliveryを実現するツールです。ArgoCDでは、Kubernetesマニフェストファイルが格納されたGitHub / Helmリポジトリを監視し、リポジトリ上で変更が発生したらその差分を検出して、Kubernetesクラスターに反映します。

GitOpsとは

ArgoCDは、いわゆるGitOpsを実現するツールとして注目されています。GitOpsとは、以下のような特徴を備えたContinuous Deliveryの手法です。

  • GitをSingle Source of Truth(信頼できる唯一の情報源)として扱い、バージョン管理や変更履歴、ピアレビュー、ロールバックなどを、kubectlのようなツールを利用せずに実現する

    • システム全体の状態をバージョン管理下に置き、それは単一のGitリポジトリに表現される(管理される)
    • オペレーションによる変更はpull requestで行われる
    • Gitで管理された状態と比較して、何らかの変更が実際の稼働システム上に発生すると、ある種のツールはそれを差分として検出し、必要に応じてそれらを同期する。
    • ロールバック・監査ログもGit経由で提供される
  • クラウドKubernetesを利用するシステムと相性が良い

    • コードで表現することのできるクラウドやコンテナリソースはGitをSingle Source of Truthとして使うのと相性が良く、コード化することでバージョン管理ができる。これによりシステムに対する操作・回復・監視をより簡単に実現できる。

図:Weaveworks Blogより参照

gitops-pipeline

gitops-description

※参考ドキュメント:

ArgoCDの構成

ArgoCDは、以下のように複数のサービスから成り立っています。

argocd-compose

API Server

ArgoCDにアクセスするWeb UI・CLI・CI/CDに利用するAPIを提供するサーバーで、アプリケーションの状態報告や管理・操作、Credential管理やGit Webhookイベントの受け取りなどを行います。

Repository Server

マニフェストファイルを管理するGitリポジトリの情報をローカルキャッシュとして保持するサーバーで、リポジトリURLなどの情報をインプットとして、Kubernetesマニフェストファイルの生成を行います。

Repository Serverは/tmp配下にリポジトリをCloneします。リポジトリの数やファイル数が多い場合はPersistent Volumeをマウントすることでエラー無く実行することが可能になります。

Repository Serverはデフォルトで3分ごとにマニフェストの更新がないかチェックします。ArgoCDでは、リポジトリに変更があったときのみマニフェストが変更されると想定しており、生成されたマニフェストを24時間キャッシュします。

一つのRepository Serverは、一つのリポジトリに対する操作は同時に一つしか実行しません。同じリポジトリに複数のアプリケーションを所有する場合、Repository Serverのレプリカ数を増加します。

Repository Serverでは、リポジトリの設定時にHEADrevisionのような設定をすると、リポジトリの更新を確認するためにgit ls-remoteを頻繁に実行します。

Application Controller

Kubernetes Controllerとして、起動中のアプリケーションを監視し、実際に稼働するアプリケーションの状態とあるべき状態とを比較します。アプリケーションがOutOfSyncの状態になったことを検知すると適切な操作を行ったり、ユーザーの定義した通りにライフサイクルイベントを実行したりします。

Controllerは、アプリケーションの reconcilliationとアプリケーションの同期のために2種類のQueueを利用します。これらは実行時間に差があり(reconcilliation < Sync)、また利用者がコントロールすることができます。

Controllerは内部でkubectlコマンドを実行し、クラスターへのリソース作成や変更を行います。PodのOOM Killを引き起こす場合は--kubectl-parallelism-limitを利用し、同時に実行するkubectl fork/execの数を制限することができます。

※参考ドキュメント:

ArgoCDの機能

ArgoCDでは、リポジトリとの同期・差分検出を行うだけでなく、それに関連した様々な機能を提供します。

手動・自動でのアプリケーション同期

ArgoCDではGitHubリポジトリを登録し、リポジトリに格納されたファイルを同期することで、マニフェストファイルの変更を検知します。リポジトリの状態と実際の起動したアプリケーションとを同期するには、自動・手動のいずれかを利用できます。

自動同期はsyncPolicyを設定することで有効になります。これを利用すると、CI/CDパイプラインがArgoCD API Serverと直接通信することなく、アプリケーションのデプロイを実行することができます。またGitリポジトリ上のリソースが削除されたとき、デフォルトではそれらを削除するがありませんが、自動的にpruneするよう設定することも可能です。

自動同期をするタイミングはSync Windowsという機能で制限することができます。Sync WindowsではCronの形式で、同期を許可・拒否する時間帯を指定します。Sync Windowsを利用するには、CLIから設定を行うか、AppProjectというマニフェストを作成します。

手動同期では、UI上で同期を行うリソースを選択することができます。

また自動検知はデフォルトで3分毎に実行しますが、Git Provider Webhookを設定することで、Webhook イベントを受け取ることができます。これによりポーリングの間隔と実際の変更のタイミングとのずれによる遅延が発生しなくなります。

※参考ドキュメント:

Web UIを備える

ArgoCDにはWeb UIがはじめから備わっており、GitHubリポジトリKubernetesクラスターの登録、アプリケーションの同期やロールバック、起動状態の確認などを行うことができます。

アプリケーションには、GitHubリポジトリに格納されたアプリケーションのバージョンが表示され、指定したバージョンへのロールバックを実行することができます。

アプリケーションに対しては、Kubernetesリソースに対するヘルスチェックを通じてリアルタイムでの起動状態を表示します。またヘルスチェックをカスタムすることも可能(Luaで記述)で、ArgoCDが対応していないCustom Resourceや、アプリケーションに既知の問題が含まれる場合に対応することが可能です。

また、CSSファイルを用意してUIをカスタマイズすることも可能なようです。

※参考ドキュメント:

複数のKubernetesマニフェスト管理ツールに対応

ArgoCDは、Kubernetesの素のマニフェストファイルに加え、以下のようなマニフェスト管理ツールに対応しています。

またConfig Management Pluginを利用することで、より多くのツールを利用することができます。

※参考ドキュメント:

複数のアプリケーション・Kubernetesクラスターに対応

ArgoCDは複数のアプリケーションを同時に管理することが可能です。現在は1000以上のアプリケーションを利用するとUIの速度が大きく低下する様ですが、将来的には2000以上のアプリケーションに対応できるよう検討されているようです。

またArgoCDは複数のKubernetesクラスターを管理対象として登録することができます。現在対応しているクラスター数の上限は不明ですが、将来的には1つのArgoCD Controllerで100クラスター以上を管理できるよう、Controllerの水平スケールやシャーディングの実装が検討されているようです。

※参考ドキュメント:

ユーザー管理

ArgoCDは初期ユーザーとしてadminが登録されており、ユーザーを追加する場合はConfigMapを利用します。

またArgoCDはSSOにも対応しています。ArgoCDをデプロイする際に利用できるinstall.ymlには、ArgoCDのほかにDex Serverが定義されており、OIDC Providerとしてこちらを利用することができます。また、既に別のOIDC Providerを利用している場合はそちらも利用できます。対応するOIDC Providerは以下の通りです。

※参考ドキュメント:

Sync Operationの設定

ArgoCDはGitHubリポジトリとのSync(同期)はResource Hookを利用して行われます。Syncを行う過程では、その前後で複数の操作を行うことが可能です。ArgoCDで定義するアプリケーションに含まれるマニフェストに指定のAnnotationを付与することでこの操作は可能であり、利用できるHookには以下の種類があります。

  • PreSync: アプリケーションのマニフェストファイルを利用する前に実行します。新しいアプリケーションをデプロイする前にDBスキーマの移行を行う場合などに利用されます。
  • Sync: PreSyn Hookがすべて完了・成功した後に実行されます。アプリケーションのマニフェストファイルを利用するのと同じタイミングです。Kubernetesで利用できるRolling Updateよりも複雑なデプロイ方法をとる場合などに利用できます。
  • Skip: アプリケーションのマニフェストファイルをスキップします。
  • PostSync: SyncHookがすべて完了・成功し、アプリケーションの状態がHealthyになった後で実行されます。アプリケーションのデプロイが完了した後にヘルスチェックなどを別途行う場合などに利用されます。
  • SyncFail: Sync操作が失敗したときに実行されます。デプロイに失敗した場合にリソースのクリーンアップを行う場合などに利用できます。

なおBlue-Green DeploymentやCanary Releaseを利用したい場合は、同プロジェクトにあるArgo Rolloutを利用することで実現できます。

※参考ドキュメント:

その他
  • Audit: Kubernetesクラスター上でのアプリケーションのイベント情報を確認することができるので、GitHub上の履歴だけでなく、実際に稼働したアプリケーションの情報も利用することができます。
  • CLI: argocd CLIを利用することで、自動化及びCIとの統合が可能になります。
  • Prometheus metrics: アプリケーション向け・ArgoCD API Server向けのメトリクスが公開されています。

ArgoCDの利用方法

ArgoCDの概要について紹介したので、次は実際にArgoCDを動かしてみます。今回は公式ドキュメントにあるGetting Startedに従って利用します。

構築環境

今回利用するKubernetesの環境は以下の通りです。

  • Kubernetes Cluster: Amazon EKS
  • 構築方法: eksctlを利用
  • 作業環境: WSL (Ubuntu 18.04.4)

※参考ドキュメント:

ArgoCDのデプロイ

クラスターが用意できたので、まずはArgoCDをクラスター上にデプロイします。ここでは事前に用意されたinstall.ymlを使用します。install.ymlには、ArgoCDを動作するうえで必要な3つのサービスに加え、redis-server dex-serverの2つが定義されています。

$ kubectl get ns
NAME              STATUS   AGE
default           Active   15m
kube-node-lease   Active   15m
kube-public       Active   15m
kube-system       Active   15m


# “argocd” Namespaceの作成
$ kubectl create namespace argocd
namespace/argocd created

# ArgoCDのデプロイ
$ kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
customresourcedefinition.apiextensions.k8s.io/applications.argoproj.io created
customresourcedefinition.apiextensions.k8s.io/appprojects.argoproj.io created
serviceaccount/argocd-application-controller created
serviceaccount/argocd-dex-server created
serviceaccount/argocd-server created
role.rbac.authorization.k8s.io/argocd-application-controller created
role.rbac.authorization.k8s.io/argocd-dex-server created
role.rbac.authorization.k8s.io/argocd-server created
clusterrole.rbac.authorization.k8s.io/argocd-application-controller created
clusterrole.rbac.authorization.k8s.io/argocd-server created
rolebinding.rbac.authorization.k8s.io/argocd-application-controller created
rolebinding.rbac.authorization.k8s.io/argocd-dex-server created
rolebinding.rbac.authorization.k8s.io/argocd-server created
clusterrolebinding.rbac.authorization.k8s.io/argocd-application-controller created
clusterrolebinding.rbac.authorization.k8s.io/argocd-server created
configmap/argocd-cm created
configmap/argocd-gpg-keys-cm created
configmap/argocd-rbac-cm created
configmap/argocd-ssh-known-hosts-cm created
configmap/argocd-tls-certs-cm created
secret/argocd-secret created
service/argocd-dex-server created
service/argocd-metrics created
service/argocd-redis created
service/argocd-repo-server created
service/argocd-server-metrics created
service/argocd-server created
deployment.apps/argocd-application-controller created
deployment.apps/argocd-dex-server created
deployment.apps/argocd-redis created
deployment.apps/argocd-repo-server created
deployment.apps/argocd-server created


# デプロイ後の確認
$ kubectl get crd
NAME                                         CREATED AT
applications.argoproj.io                     2020-09-15T05:54:39Z★
appprojects.argoproj.io                      2020-09-15T05:54:39Z★
eniconfigs.crd.k8s.amazonaws.com             2020-09-15T05:38:20Z
securitygrouppolicies.vpcresources.k8s.aws   2020-09-15T05:38:22Z

$ kubectl get sa -n argocd
NAME                            SECRETS   AGE
argocd-application-controller   1         6m55s
argocd-dex-server               1         6m55s
argocd-server                   1         6m55s
default                         1         7m10s

$ kubectl get role -n argocd
NAME                            AGE
argocd-application-controller   9m10s
argocd-dex-server               9m10s
argocd-server                   9m10s

$ kubectl get rolebinding -n argocd
NAME                            AGE
argocd-application-controller   9m17s
argocd-dex-server               9m17s
argocd-server                   9m17s

$ kubectl get clusterrole -n argocd
NAME                                                                   AGE
admin                                                                  25m
argocd-application-controller                                          9m24s
argocd-server                                                          9m24s
aws-node                                                               25m

$ kubectl get clusterrolebinding -n argocd
NAME                                                   AGE
argocd-application-controller                          10m
argocd-server                                          10m
aws-node                                               26m

$ kubectl get cm -n argocd
NAME                        DATA   AGE
argocd-cm                   0      5m47s
argocd-gpg-keys-cm          0      5m47s
argocd-rbac-cm              0      5m47s
argocd-ssh-known-hosts-cm   1      5m47s
argocd-tls-certs-cm         0      5m47s

$ kubectl get secret -n argocd
NAME                                        TYPE                                  DATA   AGE
argocd-application-controller-token-c7kh2   kubernetes.io/service-account-token   3      10m
argocd-dex-server-token-2pnlw               kubernetes.io/service-account-token   3      10m
argocd-secret                               Opaque                                5      10m
argocd-server-token-kvdr4                   kubernetes.io/service-account-token   3      10m
default-token-ctgsz                         kubernetes.io/service-account-token   3      11m

$ kubectl get svc -n argocd
NAME                    TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                      AGE
argocd-dex-server       ClusterIP   10.100.252.138   <none>        5556/TCP,5557/TCP,5558/TCP   11m
argocd-metrics          ClusterIP   10.100.0.152     <none>        8082/TCP                     11m
argocd-redis            ClusterIP   10.100.56.122    <none>        6379/TCP                     11m
argocd-repo-server      ClusterIP   10.100.67.88     <none>        8081/TCP,8084/TCP            11m
argocd-server           ClusterIP   10.100.114.29    <none>        80/TCP,443/TCP               11m
argocd-server-metrics   ClusterIP   10.100.150.172   <none>        8083/TCP                     11m

$ kubectl get deployment -n argocd
NAME                            READY   UP-TO-DATE   AVAILABLE   AGE
argocd-application-controller   1/1     1            1           11m
argocd-dex-server               1/1     1            1           11m
argocd-redis                    1/1     1            1           11m
argocd-repo-server              1/1     1            1           11m
argocd-server                   1/1     1            1           11m

$ kubectl get replicaset -n argocd
NAME                                       DESIRED   CURRENT   READY   AGE
argocd-application-controller-6bf955dd55   1         1         1       11m
argocd-dex-server-7c8f5cc655               1         1         1       11m
argocd-redis-54b6ff7bf6                    1         1         1       11m
argocd-repo-server-56966df94f              1         1         1       11m
argocd-server-cf8dbb7bd                    1         1         1       11m

$ kubectl get pods -n argocd
NAME                                             READY   STATUS    RESTARTS   AGE
argocd-application-controller-6bf955dd55-9wtrg   1/1     Running   0          11m
argocd-dex-server-7c8f5cc655-vkztr               1/1     Running   0          11m
argocd-redis-54b6ff7bf6-98hl7                    1/1     Running   0          11m
argocd-repo-server-56966df94f-rqkg6              1/1     Running   0          11m
argocd-server-cf8dbb7bd-bnl96                    1/1     Running   0          11m

ArgoCD CLIインストール

次にargocd CLIをインストールします。この時点ではArgoCD API Serverとの疎通が取れない状態なので、argocd versionコマンド実行時にエラーが発生します。

# CLIインストール
$ wget https://github.com/argoproj/argo-cd/releases/download/v1.7.4/argocd-linux-amd64
$ sudo cp -p argocd-linux-amd64 /usr/local/bin/argocd
$ sudo chmod +x /usr/local/bin/argocd

# インストール後の確認
$ argocd version
argocd: v1.7.4+f8cbd6b
  BuildDate: 2020-09-05T02:44:27Z
  GitCommit: f8cbd6bf432327cc3b0f70d23b66511bb906a178
  GitTreeState: clean
  GoVersion: go1.14.1
  Compiler: gc
  Platform: linux/amd64
FATA[0000] Argo CD server address unspecified

ArgoCD API Serverへのアクセス

つぎにArgoCD API Serverへのアクセスができるよう、argocd-serverServiceを一部変更します。

# LoadBalancerに変更
$ kubectl patch svc argocd-server -n argocd -p '{"spec": {"type": "LoadBalancer"}}'
service/argocd-server patched

# 変更後の確認
$ kubectl get svc -n argocd
NAME                    TYPE           CLUSTER-IP       EXTERNAL-IP
              PORT(S)                      AGE
argocd-dex-server       ClusterIP      10.100.252.138   <none>
              5556/TCP,5557/TCP,5558/TCP   66m
argocd-metrics          ClusterIP      10.100.0.152     <none>
              8082/TCP                     66m
argocd-redis            ClusterIP      10.100.56.122    <none>
              6379/TCP                     66m
argocd-repo-server      ClusterIP      10.100.67.88     <none>
              8081/TCP,8084/TCP            66m
argocd-server           LoadBalancer   10.100.114.29    a36182a4c9e57495c8e0f245b2d2d8a8-995334307.ap-northeast-1.elb.amazonaws.com   80:30629/TCP,443:30865/TCP   66m
argocd-server-metrics   ClusterIP      10.100.150.172   <none>
              8083/TCP                     66m

ArgoCDへのログイン(CLI

つぎにargocd CLIからログインし、ArgoCDでデプロイを行うクラスターを指定します。ここでログインをしておかないと、クラスターの登録などが実行できません。

# ログイン前にクラスターを登録しようとすると失敗する
$ argocd cluster add ubuntu-cli-user@eks-work-cluster.ap-northeast-1.eksctl.io
INFO[0000] ServiceAccount "argocd-manager" created in namespace "kube-system"
INFO[0000] ClusterRole "argocd-manager-role" created
INFO[0000] ClusterRoleBinding "argocd-manager-role-binding" created
FATA[0001] Argo CD server address unspecified

# ログインパスワードの確認
# 初期アカウントは”admin”のみ、ログインパスワードは”argocd-server”Pod名
$ kubectl get pods -n argocd -l app.kubernetes.io/name=argocd-server -o name | cut -d'/' -f 2
argocd-server-cf8dbb7bd-bnl96

# CLI経由でログイン (先ほど作成したLBのEXTERNAL-IPを指定)
$ argocd login --insecure a36182a4c9e57495c8e0f245b2d2d8a8-995334307.ap-northeast-1.elb.amazonaws.com:443
Username: admin
Password:
'admin' logged in successfully
Context 'a36182a4c9e57495c8e0f245b2d2d8a8-995334307.ap-northeast-1.elb.amazonaws.com:443' updated

# パスワードのアップデート
$ argocd account update-password
*** Enter current password:
*** Enter new password:
*** Confirm new password:
Password updated
Context 'a36182a4c9e57495c8e0f245b2d2d8a8-995334307.ap-northeast-1.elb.amazonaws.com:443' updated

# ログイン後の確認
# 先ほどはエラー表示だったArgoCD Serverの情報が確認できる
$ argocd version
argocd: v1.7.4+f8cbd6b
  BuildDate: 2020-09-05T02:44:27Z
  GitCommit: f8cbd6bf432327cc3b0f70d23b66511bb906a178
  GitTreeState: clean
  GoVersion: go1.14.1
  Compiler: gc
  Platform: linux/amd64
argocd-server: v1.7.3+b4c79cc
  BuildDate: 2020-09-01T23:19:02Z
  GitCommit: b4c79ccb88173604c3786dcd34e83a9d7e8919a5
  GitTreeState: clean
  GoVersion: go1.14.1
  Compiler: gc
  Platform: linux/amd64
  Ksonnet Version: v0.13.1
  Kustomize Version: {Version:kustomize/v3.6.1 GitCommit:c97fa946d576eb6ed559f17f2ac43b3b5a8d5dbd BuildDate:2020-05-27T20:47:35Z GoOs:linux GoArch:amd64}
  Helm Version: version.BuildInfo{Version:"v3.2.0", GitCommit:"e11b7ce3b12db2941e90399e874513fbd24bcb71", GitTreeState:"clean", GoVersion:"go1.13.10"}
  Kubectl Version: v1.17.8

# クラスター登録
$ argocd cluster add
ERRO[0000] Choose a context name from:
CURRENT  NAME                                                       CLUSTER                                    SERVER
*        ubuntu-cli-user@eks-work-cluster.ap-northeast-1.eksctl.io  eks-work-cluster.ap-northeast-1.eksctl.io  https://987C1007C84B9FEEA7CC9F1D529C3393.yl4.ap-northeast-1.eks.amazonaws.com

$ argocd cluster add ubuntu-cli-user@eks-work-cluster.ap-northeast-1.eksctl.io
INFO[0000] ServiceAccount "argocd-manager" already exists in namespace "kube-system"
INFO[0000] ClusterRole "argocd-manager-role" updated
INFO[0000] ClusterRoleBinding "argocd-manager-role-binding" updated
Cluster 'https://987C1007C84B9FEEA7CC9F1D529C3393.yl4.ap-northeast-1.eks.amazonaws.com' added

この時点でWeb UIからログインをしている場合は、以下の画像のように新規クラスターが登録されたことが確認できます。

f:id:FY0323:20200922094630p:plain

ArgoCDへのログイン(UI)

次にWeb UIからログインをします。先ほど作成したLBのExternal-IPを指定して、Webブラウザからアクセスをすると、ログイン画面が表示されます。

f:id:FY0323:20200922095617p:plain

先ほど変更したパスワードとadminユーザーを指定してログインをします。この時点ではまだ何のアプリケーションも登録されていません。

f:id:FY0323:20200922095711p:plain

サンプルアプリケーションの作成

次にWeb UIからアプリケーションを作成します。まずはNEW APPを選択します。

f:id:FY0323:20200922095814p:plain

すると、以下のように入力画面が表示されます。

f:id:FY0323:20200922095939p:plain

Application Nameには作成するアプリケーションの名称を指定します。Projectは複数のアプリケーションを論理的にグルーピングしたものを指し、特に複数のチームが共通のArgoCDを利用する場合に役立ちます。ここではdefaultを指定しますが、特に指定しないアプリケーションはdefaultProjectに属することになります。

SYNC POLICYではManual Automaticから選択することができます。ここではManualを選択しますが、Automaticを選択した場合は以下のように追加の選択オプションが表示されます。

f:id:FY0323:20200922100013p:plain

またSYNC OPTIONSは特に変更せず、次に進みます。

※参考ドキュメント:

次に同期元となるGitリポジトリを指定します。

f:id:FY0323:20200922100055p:plain

今回はArgoCDのサンプル用マニフェストファイルの格納されているこちらを利用します。利用するマニフェストファイルはリポジトリguestbookに含まれるため、pathにはこれを指定します。またrevisionHEADを指定します。

次にアプリケーションをデプロイするクラスターを指定します。

f:id:FY0323:20200922100152p:plain

ここでは、ArgoCDが稼働しているのと同じクラスターを表すhttps://kubernetes.default.svcを指定します。なおクラスターを事前に登録することで、この項目の選択肢としてクラスターの情報が表示されます。

必要な情報はすべて設定したので、画面上部のCREATEを選択します。すると以下の画面のように遷移し、アプリケーションが登録されたことがわかります。

f:id:FY0323:20200922100243p:plain

この時点ではOutOfSyncと表示されている通り、まだGitリポジトリとの同期が行われておらず、クラスター上にもアプリケーションは存在しません。UI上で作成したアプリケーションを選択しても、アプリケーションを構成するKubernetesリソースの構成は見えるものの、やはりOutOfSync状態であることが確認できます。

f:id:FY0323:20200922100329p:plain

この時コマンドライン上で確認をすると、以下のように表示されます。

# アプリケーションは登録されている
$ argocd app list
NAME       CLUSTER                         NAMESPACE  PROJECT  STATUS     HEALTH   SYNCPOLICY  CONDITIONS  REPO                                                 PATH       TARGET
guestbook  https://kubernetes.default.svc  default    default  OutOfSync  Missing  <none>      <none>      https://github.com/argoproj/argocd-example-apps.git  guestbook  HEAD

# アプリケーションの詳細
$ argocd app get guestbook
Name:               guestbook
Project:            default
Server:             https://kubernetes.default.svc
Namespace:          default
URL:                https://a36182a4c9e57495c8e0f245b2d2d8a8-995334307.ap-northeast-1.elb.amazonaws.com/applications/guestbook
Repo:               https://github.com/argoproj/argocd-example-apps.git
Target:             HEAD
Path:               guestbook
SyncWindow:         Sync Allowed
Sync Policy:        <none>
Sync Status:        OutOfSync from HEAD (6bed858)
Health Status:      Missing

GROUP  KIND        NAMESPACE  NAME          STATUS     HEALTH   HOOK  MESSAGE
       Service     default    guestbook-ui  OutOfSync  Missing
apps   Deployment  default    guestbook-ui  OutOfSync  Missing

# クラスター上にはアプリケーションリソースが存在しない
$ kubectl get pods
No resources found in default namespace.

 kubectl get svc
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.100.0.1   <none>        443/TCP   3h35m



# “Application”というCustom Resouceは作成されている
$ kubectl get app -n argocd
NAME        AGE
guestbook   37m

次にSyncを実行します。今回はargocdCLIからSyncを実行します。

# 同期の実行
$ argocd app sync guestbook
TIMESTAMP                  GROUP        KIND   NAMESPACE                  NAME    STATUS    HEALTH        HOOK  MESSAGE
2020-09-15T18:14:25+09:00            Service     default          guestbook-ui  OutOfSync  Missing
2020-09-15T18:14:25+09:00   apps  Deployment     default          guestbook-ui  OutOfSync  Missing
2020-09-15T18:14:25+09:00            Service     default          guestbook-ui    Synced  Healthy
2020-09-15T18:14:25+09:00            Service     default          guestbook-ui    Synced   Healthy              service/guestbook-ui created
2020-09-15T18:14:25+09:00   apps  Deployment     default          guestbook-ui  OutOfSync  Missing              deployment.apps/guestbook-ui created
2020-09-15T18:14:25+09:00   apps  Deployment     default          guestbook-ui    Synced  Progressing              deployment.apps/guestbook-ui created

Name:               guestbook
Project:            default
Server:             https://kubernetes.default.svc
Namespace:          default
URL:                https://a36182a4c9e57495c8e0f245b2d2d8a8-995334307.ap-northeast-1.elb.amazonaws.com/applications/guestbook
Repo:               https://github.com/argoproj/argocd-example-apps.git
Target:             HEAD
Path:               guestbook
SyncWindow:         Sync Allowed
Sync Policy:        <none>
Sync Status:        Synced to HEAD (6bed858)
Health Status:      Progressing

Operation:          Sync
Sync Revision:      6bed858de32a0e876ec49dad1a2e3c5840d3fb07
Phase:              Succeeded
Start:              2020-09-15 18:14:25 +0900 JST
Finished:           2020-09-15 18:14:26 +0900 JST
Duration:           1s
Message:            successfully synced (all tasks run)

GROUP  KIND        NAMESPACE  NAME          STATUS  HEALTH       HOOK  MESSAGE
       Service     default    guestbook-ui  Synced  Healthy            service/guestbook-ui created
apps   Deployment  default    guestbook-ui  Synced  Progressing        deployment.apps/guestbook-ui created


# 同期後の確認
$ argocd app get guestbook
Name:               guestbook
Project:            default
Server:             https://kubernetes.default.svc
Namespace:          default
URL:                https://a36182a4c9e57495c8e0f245b2d2d8a8-995334307.ap-northeast-1.elb.amazonaws.com/applications/guestbook
Repo:               https://github.com/argoproj/argocd-example-apps.git
Target:             HEAD
Path:               guestbook
SyncWindow:         Sync Allowed
Sync Policy:        <none>
Sync Status:        Synced to HEAD (6bed858)
Health Status:      Healthy

GROUP  KIND        NAMESPACE  NAME          STATUS  HEALTH   HOOK  MESSAGE
       Service     default    guestbook-ui  Synced  Healthy        service/guestbook-ui created
apps   Deployment  default    guestbook-ui  Synced  Healthy        deployment.apps/guestbook-ui created

# 他のリソースも作成される
$ kubectl get all
NAME                                READY   STATUS    RESTARTS   AGE
pod/guestbook-ui-65b878495d-cs87p   1/1     Running   0          2m53s

NAME                   TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
service/guestbook-ui   ClusterIP   10.100.247.168   <none>        80/TCP    2m53s
service/kubernetes     ClusterIP   10.100.0.1       <none>        443/TCP   3h39m

NAME                           READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/guestbook-ui   1/1     1            1           2m53s

NAME                                      DESIRED   CURRENT   READY   AGE
replicaset.apps/guestbook-ui-65b878495d   1         1         1       2m53s

この時Web UIでも同期が行われていることが確認できます。

f:id:FY0323:20200922100441p:plain

しばらく待機すると、以下のようにSynced Healthyという状態が確認できます。

f:id:FY0323:20200922100519p:plain

アプリケーションを選択すると、Kubernetesリソースの構成が表示されます。

f:id:FY0323:20200922100628p:plain

f:id:FY0323:20200922100638p:plain

アプリケーションの変更

次にアプリケーションの構成を一部変更してみます。先ほどのWeb UIからguestbook-ui Deploymentリソースを選択すると、以下のようにYamlファイルが表示されます。

f:id:FY0323:20200922100716p:plain

ここでGitリポジトリと異なる状態を作るために、spec.replicas3に変更し、SAVEを選択します。すると、Web UI上では新たに2つのPodが作成される様子が見られます。またアプリケーションはOutOfSyncの状態となることも確認できます。

f:id:FY0323:20200922100754p:plain

コマンドラインから確認しても、Podが追加されたことが確認できます。

$ kubectl get all
NAME                                READY   STATUS    RESTARTS   AGE
pod/guestbook-ui-65b878495d-cs87p   1/1     Running   0          16m
pod/guestbook-ui-65b878495d-jp559   1/1     Running   0          3m30s★
pod/guestbook-ui-65b878495d-rcqlw   1/1     Running   0          3m30s★

NAME                   TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
service/guestbook-ui   ClusterIP   10.100.247.168   <none>        80/TCP    16m
service/kubernetes     ClusterIP   10.100.0.1       <none>        443/TCP   3h52m

NAME                           READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/guestbook-ui   3/3     3            3           16m

NAME                                      DESIRED   CURRENT   READY   AGE
replicaset.apps/guestbook-ui-65b878495d   3         3         3       16m

このときWeb UIでAPP DIFFを選択すると、Gitリポジトリで定義された状態と実際のアプリケーションの状態の間にある差分が表示されます。

f:id:FY0323:20200922100949p:plain

Gitリポジトリの状態に戻す(=spec.replicas1に戻す)ため、Web UI上のHISTORY AND ROLLBACKを選択します。すると、変更前のアプリケーションの状態を表すGitリポジトリのRevisionが表示されるので、Redeployを選択します。

f:id:FY0323:20200922101031p:plain

再デプロイの実行を確認し、OKを選択します。

f:id:FY0323:20200922101117p:plain

Gitリポジトリの状態にロールバックされ、先ほど作成した2つのPodが削除される様子が確認できます。

f:id:FY0323:20200922101203p:plain

削除

最後にアプリケーション、及びArgoCDの削除を行います。Web UIからDELETEを選択すると、削除の確認を求められるのでOKを選択します。

f:id:FY0323:20200922101251p:plain

Kubernetesリソースの削除が開始され、しばらくするとすべてのリソースが削除されたことが確認できます。

f:id:FY0323:20200922101401p:plain

f:id:FY0323:20200922101411p:plain

コマンドラインからも、アプリケーションの削除が確認できます。

$ kubectl get all
NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
service/kubernetes   ClusterIP   10.100.0.1   <none>        443/TCP   4h13m

$ kubectl get app -n argocd
No resources found in argocd namespace.

最後にArgoCDを削除します。

$ kubectl delete -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
warning: deleting cluster-scoped resources, not scoped to the provided namespace
customresourcedefinition.apiextensions.k8s.io "applications.argoproj.io" deleted
customresourcedefinition.apiextensions.k8s.io "appprojects.argoproj.io" deleted
serviceaccount "argocd-application-controller" deleted
serviceaccount "argocd-dex-server" deleted
serviceaccount "argocd-server" deleted
role.rbac.authorization.k8s.io "argocd-application-controller" deleted
role.rbac.authorization.k8s.io "argocd-dex-server" deleted
role.rbac.authorization.k8s.io "argocd-server" deleted
clusterrole.rbac.authorization.k8s.io "argocd-application-controller" deleted
clusterrole.rbac.authorization.k8s.io "argocd-server" deleted
rolebinding.rbac.authorization.k8s.io "argocd-application-controller" deleted
rolebinding.rbac.authorization.k8s.io "argocd-dex-server" deleted
rolebinding.rbac.authorization.k8s.io "argocd-server" deleted
clusterrolebinding.rbac.authorization.k8s.io "argocd-application-controller" deleted
clusterrolebinding.rbac.authorization.k8s.io "argocd-server" deleted
configmap "argocd-cm" deleted
configmap "argocd-gpg-keys-cm" deleted
configmap "argocd-rbac-cm" deleted
configmap "argocd-ssh-known-hosts-cm" deleted
configmap "argocd-tls-certs-cm" deleted
secret "argocd-secret" deleted
service "argocd-dex-server" deleted
service "argocd-metrics" deleted
service "argocd-redis" deleted
service "argocd-repo-server" deleted
service "argocd-server-metrics" deleted
service "argocd-server" deleted
deployment.apps "argocd-application-controller" deleted
deployment.apps "argocd-dex-server" deleted
deployment.apps "argocd-redis" deleted
deployment.apps "argocd-repo-server" deleted
deployment.apps "argocd-server" deleted

$ kubectl delete namespace argocd
namespace "argocd" deleted

本番環境での利用に向けて

ArgoCDは本番環境での稼働実績もあり、また利用するうえでのBest Practiceなども公開されています。最後にこのBest Practiceを眺め、ArgoCDを利用するうえでのポイントについて紹介します。

アプリケーション用リポジトリマニフェストリポジトリを用意する

アプリケーションのソースコードマニフェストファイルを1つのリポジトリに格納すると、以下のような問題が発生します。

  • アプリケーションに対する全てのCommitがデプロイにつながる。例えば、マニフェストファイルの一部を変更するだけの作業でパイプラインを起動したくない、という場合に対応できなくなる。
  • 誰でもアプリケーションをリリースできるようになるが、リリースできるメンバーを制限することが難しくなる。
  • Git Logにはアプリケーションの変更とデプロイの履歴が混在することになる。Audit Logをキレイにすることにつながる。
  • 複数のリポジトリからなるマイクロサービスの場合、それぞれのサービスのバージョンスキーマやリリースサイクルが異なるため、そのうちの一つのリポジトリマニフェストファイルを一緒に格納すると、意味をなさなくなってしまう場合がある。
  • CIパイプラインを自動化している場合、マニフェストファイルをpushすることで、ビルドジョブとGit Commit Triggerの無限ループとなる場合がある。

上記問題を解消するため、アプリケーションのソースコードマニフェストファイルはそれぞれ別のリポジトリに格納することが推奨されています。

デプロイ用リポジトリの数を適切にする

マニフェストファイルを格納するようなリポジトリをいくつ用意するかは、状況によってその答えが変わってきます。そのため、以下のようなポイントを考慮して決める必要があります。

  • 単一のリポジトリ:小規模の会社で自動化をほぼ行わず、全てのメンバーを信頼している
  • チームごとにリポジトリを用意する:中規模の会社である程度の自動化を行う
  • サービスごとにリポジトリを用意する:大規模な会社で自動化を推進しており、メンバー毎のアクセスなどをコントロールする必要がある

チーム単位で自分たちのリポジトリを所有することで、誰がリリースするかを決定・制御することができます。これにより、リリースのボトルネックとなる単一の中央集権的チームを所有したり、全チームに書き込み権限を与える場合と比べて、自分たち自身でリポジトリの管理を行うことができます。

Commitする前にテストを行う

多くのエンジニアがマニフェストファイルの変更をCommit/Pushし、GitOpsのエージェントはアプリケーションがデプロイできるかによってその変更をチェックします。一方、Commit前にテストを行うことで防げたかもしれないような問題が、運用前の環境に入り込み、なかなか表出しなくなる、という自体も発生する場合があります。

GitOpsエージェントは一般的にCLIコマンドを実行することでマニフェストの生成を行うので、同じコマンドを使えば、ローカル環境でもテストを行うことができます。

Gitマニフェストは外部の変更によって変更すべきではない

KustomizeやHelmといったツールは、同じCommitでも異なるテンプレートマニフェストとすることを可能にします。もしもGit Commit無しでマニフェストファイルが変更されると、以下のような問題が発生します。

そのため、KustomizeやhElmを利用する場合は、特定のCommitにピンを打つことが推奨されます。

# Kustomizeの場合
bases:
- github.com/argoproj/argo-cd//manifests/cluster-install?ref=v0.11.1

# Helmの場合
dependencies:
- name: argo-cd
 version: 0.6.1
 repository: github.com/argoproj/argo-cd/manifests/cluster-install

またこれと関連し、Gitマニフェスト中にすべてを定義しないことも推奨されています。例えば、Deploymentのspec.replicasの数をHPAによって制御する場合、Gitマニフェストにレプリカ数を記述してしまうと、Commitのたびにレプリカ数が戻ってしまいます。そのため、外部要因によって動的に変更するパラメータについては、Gitマニフェスト内では管理しないようにすることが推奨されます。

Secretの管理方法を検討する

アプリケーションコードやGitマニフェストに秘匿情報を直接書き込むことはセキュリティ上の問題につながるため、多くはKubernetesのSecretリソースなどを利用します。その他の選択肢としては以下のようなものがあるので、GitOpsを開始する前にSecret情報の扱いについて検討する必要があります。

  • Sealed Secret
  • External Secret
  • Vault
  • Helm Secret
  • Kustomize secret generator plugins

※参考ドキュメント: