TECHSTEP

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

ArgoCD ver 1.8 ~ 2.2のリリース内容を眺める

つい先日ArgoCD ver 2.2.0がリリースされたので、今回はその内容を眺めてみます。

また、以前ArgoCDを触ってからだいぶ時間が空いてしまい、その間アップデートがいくつもあったので、ver 1.8からおさらいしようと思います。

ver 2.2だけ見たい場合はこちらから飛んでください

なお、過去の性能改善系はなるべく省き、機能追加の部分に絞ってます。

1.8

ver 1.8は2020/12/10にリリースされました。

性能関連

annotation-base path detection

argocd.argoproj.io/manifest-generate-paths というannotationを利用できるようになりました。これを利用すると、ここで指定したパス以外のファイルが変更されても、不要なreconciliationをスキップできます。またこの時に前回のコミットで生成したマニフェストを再利用することで、マニフェストの生成をスキップすることもできます。

Application Controllerの水平スケール

application-controllerは、ターゲットクラスターのキャッシュとアプリのreconciliationを管理する責務を担います。これまでapplication-controllerはArgoCDの中で唯一水平スケールすることのできないコンポーネントで、特にクラスター数が数百を超えるとボトルネックとなるポイントでした。

今回cluster shardを導入することで application controllerを複数稼働できるようになりました。controllerを増やすことでクラスター接続数を増加することができ、reconciliationパフォーマンスも改善することができます。

Core Func

Namespace and CRD creation

namespaceやCRDといったリソースは、他のリソースを作成する前に作成されている必要のあるものです。そのためArgoCDでは PreSync のフェーズよりも前に作成されるのが望ましいです。

これを実現するため、namespaceに紐づいたリソースを検知すると、まずnamespaceを作るように、またCRを作る前にCRDが必要な場合もそれを検知し、作成する様な挙動に変更されました。

unknown field of built-in k8s type

ArgoCD version 1.2から sync-options: validate=false という形で指定すると、デプロイ時のvalidationをスキップすることができます( kubectl apply --validate=false と同様の仕組み)。

これまではvalidationオフの状態でsyncすると、unknown fieldはドロップされ、out-of-syncとは判定されなかったのが、このバージョンからout-of-syncになるよう変更されました。

endpoint diffing

Kubernetes Endpoint Controllerの挙動と、ArgoCDでの差分検出時の挙動がうまくかみ合わず、false-positiveな差分検知をしてしまっていたようです。リリースによりこの問題が修正されました。

app-of-apps health assessment

ArgoCDでは app-of-apps パターンという、Application自体をApplicationで管理するパターンが広く知られています。app-of-apps パターンを採用している場合、子のApplicationがunhealthyになると親のApplicationもunhealthyになっていました。この挙動は多くのユーザーにとって冗長であり、混乱を生む原因であるとして、built-inのヘルスチェックが削除されました。

なおcustom health checkを使用することで、必要に応じて機能を追加することはできます。

Global Project

 ProjectApplication の論理的グループを提供する機能で、複数チームがArgoCDを使うようなマルチテナントな環境で特に有用です。Projectではリポジトリクラスター、リソースの種別を制限するRuleを設定することができます。

Global Projectを利用することで、ほかのProjectに同じ設定を渡すことができます。 argocd-cm ConfigMapに、設定元となるプロジェクトと、それを反映するプロジェクトの条件を追記することで、該当するProjectに指定の設定を反映することができます。

利用できるパラメータは、namespaceやclusterの制限、リポジトリの指定、SyncWindowsなどになります。

コンフィグ管理ツール
  • oci base repo: HelmがOCIのサポートを開始したので、ArgoCDもそれに対応しました。OCI registry内で提供されるHelm chartを利用可能になりました。

  • configurable helm version: ArgoCDはHelm v2/3のどちらのバージョンもサポートします。ver 1.8から、Helmのどちらのバージョンを使用するべきか、chart定義のapiversionで決定されるようになりました。Helmのバージョン違いで互換性の問題が出ると、希望のバージョンで上書きできます。

UI

  • application filter: Application Listページにあるfilterを見つけやすく、条件のクリアもやりやすくなりました。

  • git tag/branch 自動補完: Gitリポジトリのブランチ・タグ名をリストで表示してくれるようになりました。

2.0

ver 2.0は2021/4/7にリリースされました。メジャーアップデートということもあり、新しい機能が多く発表されました。

ApplicationSet

ApplicationSetk8s custom resourceとして利用でき、ArgoCDを拡張することができます。ApplicationSetは先日触れましたが、主な機能としては以下の通りです。

ユースケースとしては、Kubernetesに導入する各種add-onの管理、モノレポでのArgoCDの活用などが挙げられます。特に大規模な環境だと、ArgoCD単一では管理コストが高くなるような設計に対して、 ApplicationSet の利用が役に立つケースは色々とありそうです。

ArgoCD Image Updater

ArgoCD Image UpdaterはArgoCDがApplication で利用するコンテナイメージのレジストリをモニターし、新しいコンテナイメージがアップロードされると、マニフェストを自動で書きかえて更新する機能を提供します。 これと同じ機能はFluxなどでは以前から提供されていた機能ですが、ArgoCDでもこの機能を利用することができるようになりました。

利用時はArgoCDと同じクラスター上にデプロイし、 argocd-cm ConfigMap等に必要な設定を入れれば使えるようです。

ArgoCD Notification

ArgoCDは通知サービスとの連携をするために複数の選択肢が存在しましたが、今後はArgoCD Notificationsでサポートすることになったようです。ArgoCD Notifications自体は以前から存在しましたが、コミュニティなどからのFBを受けて改善をしていたようです。

ArgoCD Notificationsも以前触れたことがありますが、導入はそれほど難しくないので、Slackなどの通知ツールをArgoCDと簡単に連携することができます。

Core feature

Resource deletion/pruning

ArgoCDは Application リソースを削除するとき、もしくはUIから個別リソースの削除を選択すると、Application Sync時に不要なリソースは削除する動きになります。

従来この処理はForegroundで処理をしていましたが、このときArgoCDが不要なリソースを削除しようとしてスタックすることがあったようです。 このバージョンではPrunePropagationPolicy=background という新しいSyncオプションを追加し、backgroundでのリソース削除を選択可能にしました。

Sync changed resources only

これまでArgoCDではSync時はリソース変更の有無にかかわらず、全てのアプリリソースにkubectl applyを実行していました。これは特に数百単位のリソースを扱う際に、Sync完了まで多くの時間を要することがありました。 ApplyOutOfSyncOnly=true というオプションを利用すると、変更リソースのみをsyncすることが可能になりました。

Prune last

PruneLast=true というオプションを追加すると、他のリソースのsyncが完了した後でpruneを実行します。Kubernetesリソース間で依存関係があるような場合に有効になります。

CRDに対するヘルスチェックの追加

ver 2.0ではSealed Secrets / External Secrets / Strimzi CRDに対応しました。

UI

  • Pods view: 全てのリソースを一度に表示するのでなく、Podと関連するリソースのみ表示できるようになりました。Podを管理する上位リソース毎、Node毎など、複数の検索候補を提供します。

  • Logs viewer: 親リソースでPodのログをまとめたり、ログストリームの有効無効化、フィルタリング機能などを実装しました。またargocd CLIではログストリームをサポートしており、 argocd app logs と打つと、Application配下の複数のPodのログを出力することができます。

  • banner feature: バナー上に通知を表示することができるようになりました。

その他

セキュリティ面では、脆弱性への対応、debianからubuntuへコンテナベースイメージの変更など行いました。

2.1

ver 2.1は2021/8/20にリリースされました。

ArgoCD Core

軽量版のArgoCD distributionとしてArgoCD Coreが発表されました。ArgoCDにはマルチテナントをサポートするため多様な機能が含まれますが、それが不要なユーザーはArgoCD Coreを利用し、必要最小限の機能だけを利用することができます。ArgoCD Coreは、ArgoCD自体の機能と、KubernetesビルトインのRBACによるアクセス制御などを提供します。

Core feature

diffing customization

ArgoCDで差分が発生してもそれを無効化するDiffingという機能があります。ver 2.1から、Diffing機能にJQパスを利用できるようになりました。

CRDに対するヘルスチェックの追加

ver 2.1で Trident / Elastic Cloud on Kubernetes / Cluster API / MinIO が追加されました。

リポジトリ登録の簡易化

これまでGitリポジトリをArgoCDに登録するには、argocd-cm ConfigMapに追加する必要がありました (argocd repo add コマンドでも可能)。それがsecretリソースを追加するだけで完了するようになりました。

Enhanced resource customaization

リソースヘルスチェックのカスタマイズが可能になりました。これまでは argocd-cmresource.customization キーを設定することで実現していました。ver 2.1では resource.customizationキーを廃止し、リソースごと(GroupKindごと?)に設定できるようにしました。これにより、kustomizeなどの管理ツールをより使いやすくなったようです。

Kubernetes Secretから秘匿情報を参照可能に

Kubernetes Secretの値をArgoCDの設定に利用することが可能になりました。 argocd-secret という名称のSecretリソースを作成する、または appkubernetes.io/part-of: argocd というラベルを付与したSecretを作成すると、別ファイルから Secretデータ情報を呼び出すことができるようになりました。

ArgoCD server processの設定

オプションとして argocd-cmd-params-cm ConfigMapが導入されました。ArgoCDは複数のサービスが連動して機能を提供しますが、それらサービスに対する設定をこのConfigMapで設定することが可能になりました。

argocd-utilの終了、argocd adminへの移行

ver 2.0で導入された argocd-util CLIが廃止され、同機能が argocd admin コマンドで提供されるようになりました。

argocd admin では、ArgoCDを管理する運用者向けのコマンドが提供されており、管理対象のクラスターのkubeconfigの出力、デプロイ前のArgoCDの設定変更のvalidate、ArgoCDオブジェクトの生成などが可能です。

UI

  • Application Listページの改善: status barの追加、検索ボックスのデザイン変更などがされました。

 

2.2

2021/12/15にリリースされました~。

Project scoped repositories and clusters

これまでのよくある運用フローとして、管理者・運用者がGitリポジトリクラスター登録を行い、開発者は新規で利用するリポジトリなどを申請する形があります。この場合、開発者が新しいリポジトリクラスターを登録したくても権限がなく、運用者の作業を待つ必要がありました。

これに対応するため、Project-scopeなリポジトリクラスターを機能として追加しました。これにより開発者は自分でリポジトリクラスターの追加を実行できるようになります。

この機能は、Project向けにRBACルールを設定して利用可能なリポジトリクラスターを制御し、開発者がRBACに適合したリソースをデプロイすることで実現できます。実際は AppProject リソース中に定義する spec.role 内でポリシーを定義することで、権限を制御するようです。

Config management plugins v2

ArgoCDでは kustomize / helmといったポピュラーなマニフェスト管理ツールと組み合わせることが可能ですが、今回リリースされる Config Management Pluginではさらに拡張可能になります。この機能ではk8sマニフェストを生成するジェネレータとしてスクリプトを利用できるようになります。

2.2ではサイドカーイメージの中でプラグインを起動します。サイドカーイメージはパッケージの依存関係をパッケージすることで、nodejs/pythonといった言語を使うこともできます。

Resource tracking

 argocdは”app.kubernetes.io/instance”というラベルによってリソースを追跡しています。これによる課題として、①ラベルとして使える文字数が63文字しかない、②他のツールがラベルを書き換えると矛盾が発生する、③1クラスターに複数ArgoCDをデプロイしたときにリソースとArgoの関係が分からない、などが出てきました。これを解決するため、新しいannotationとして argocd.argoproj.io/tracking-id を利用できるようになります。

tracking-id に変更すると、Application名だけでなくNamespaceなどの情報も追加されます。このannotationは他のKubermnetesツールとのコンフリクトは発生せず、また一つのクラスター上に複数のArgoCDを導入した場合も区別しやすくなります。

その他

その他の改善として以下のポイントが挙げられています。

  • ArgoCD RBACがregex matchをサポート: これまでの globMatch に加え、regexMatch も利用できるようになりました(こちらの例がわかりやすそうです)。

  • CRDに対するヘルスチェックの追加: あらたに KubeVirt / Cassandra / Openshift Route / Openshift DeploymentConfig / Confluent / Apache Sparkを追加しました。

  • バナー表示の位置を変更可能に: UI上のバナーの位置を画面のtop/bottomに変更可能になりました。

  • AppProjectのdestinationにCluster nameを利用可能に

ArgoCD ApplicationSetを動かしてみる

今回はArgoCDをより拡張する機能を持つ ApplicationSetを動かしてみます。

ApplicationSetとは

ApplicationSetは、Argo Projectに含まれるプロダクトの一つです。ArgoCDを利用する複数のユースケースにおいて、Applicationリソースの管理を自動化しつつ、柔軟に管理する方法を提供するControllerになります。

ArgoCDはApplicationというリソースの中で、利用するGitリポジトリマニフェストファイルの場所デプロイ先のクラスターなどを指定します。ArgoCDを利用する環境の規模が大きくなったりすると、管理をする上で面倒になるケースが出てきます。

例えば、1つのアプリケーションを提供する環境が複数のKubernetesクラスターで構成されており、それぞれのクラスターに同じアプリケーションをデプロイしようとすると、クラスターごとにApplicationリソースを作成しなければなりません。 また、monorepoのような、1つのリポジトリ中で複数のアプリケーションを管理する場合も、それぞれのアプリケーションごとに Application リソースを用意しなくてはなりません。

ApplicationSetは、上記のような悩みを解消するため、複数のクラスターやGitリポジトリを効率的に扱うための機能を提供します。

Generator

ApplicationSetは Generator というパラメータを含みます。 Generatorクラスターの名称やURL、Gitリポジトリなどのパラメータを設定することで、それぞれに対応した Application マニフェストを生成し、複数のクラスターやGitリポジトリを使用したArgoCDによるアプリケーション管理を実現します。

Generator にはいくつか種類があり、以下の通りになります。

  • List generator : クラスターの名称とアクセス先URLなどのリストを生成する
  • Cluster generator : ArgoCDに登録されているクラスター情報を生成する。ArgoCDに登録されたクラスターは自動的に検知する。
  • Git generator : Gitリポジトリリポジトリ中のフォルダ構成などの情報を生成する。
  • Matrix generator : 2種類のgeneratorを組み合わせて使う。
  • SCM Provider generator : GitHub/GitlabのようなSCMの提供するAPIを使用し、Organization中のリポジトリを探索してパラメータを生成する
  • Cluster Decision Resource generator : ArgoCDに登録されたクラスターのリストを生成する。Custom Resourceのインターフェイスとして利用し、事前にConfigMapに定義したCustom Resourceを作成する。

※ 参考:ApplicationSet Controller - Generators

ユースケース

ApplicationSetでは、いくつかのユースケースを想定しています。

Cluster add-on

Cluster add-onとは、Kubernetesクラスターに機能を追加・拡張するようなプロダクトのことを総称しており、例えば Prometheus OperatorArgo workflow などを指しています。これらは各クラスターに共通して導入することが多いため、ApplicationSetを生かすことのできるユースケースの一つとなります。

Cluster add-onはクラスターレベルの権限を必要とするため、その管理はインフラ管理者が担うことが多くなります。Add-onを導入するクラスター数の増減が多くなるほど管理コストは高くなります。またクラスターごとに利用用途が異なる(テスト・本番環境など)と、add-onの設定を変更する場合も出てくるため、より複雑になります。

ApplicationSetでは複数のGeneratorを提供しており、それぞれ用途に応じて使い分けることができます。

  • List generator : デプロイするadd-onごとにApplicationSetを用意し、デプロイ先のクラスターリストを定義することで管理できます。List generatorの場合クラスターの増減に応じてApplicationSetの手動更新が必要となります。

  • Cluster generator : デプロイするadd-onごとにApplicationSetを用意します。Cluster generatorはArgoCDに登録されたクラスターを自動で検知するため、クラスターの変更に合わせた自動更新が可能となります。

  • Git generator : Git generatorの files というfieldを使い、クラスターのリストをJSONファイルで定義しておきます。このJSONファイルを更新することでクラスター情報を更新し、add-onをデプロイすることができます。また directories というfieldを使うと、例えばクラスターごとに対応するディレクトリを用意しておき、ディレクトリ名とクラスター名を一致させておくと、ディレクトリの更新に合わせてクラスター側を更新することができます。

Monorepo

あるプロジェクトでMonorepoを採用している場合、一つのGitリポジトリにすべてのコードが格納されます。Monorepoの場合は複数チームが共通の基準に従う必要があり、それらを促進・強制するためにも、自動化が重要になってきます(参考リンク)。

Monorepoを採用する場合、Kubernetesクラスターの管理者は、1つのリポジトリからクラスター全体の状態を管理することになります。そのため、リポジトリ中のマニフェストファイルが更新されれば、その変更は即座にクラスターへと反映されるべきです。

Git generator を利用することで、monorepoを採用するプロジェクトの管理者をサポートすることが可能となります。directories fieldでアプリケーション個別に分かれたサブディレクトリを指定することが可能です。また files fieldを使うと、JSONメタデータを含むファイルを参照し、そこに定義されたアプリケーションをデプロイすることも可能です。

Self-service of ArgoCD Applications on multi-tenant cluster

開発者が自らArgoCDの Applicationを開発・利用する場合、app-of-appsというパターンと組み合わせ、クラスター管理者がPRを通じてマニフェストファイルをチェックし、デプロイの許可を行う、というケースがあります。Applicationには project cluster など重要なパラメータがあり、この設定を誤ってしまうと別のArgoCD Projectやクラスターが更新されるため、レビュアーの重要度が上がってしまいます。そのため管理者としては、開発者の変更できるパラメータを絞り、重要なものは変更できないようにしておきたくなります。

ApplicationSetでは、Git generatorと組み合わせた代替手段を提供しています。ApplicationSetの template filedに、 project destination などの情報をべた書きして固定し、 source 部分のみ Git generatorで生成するようにしておきます。開発者にはGit generator部分のみ更新してもらうことで、アプリケーションの更新先をある程度コントロールすることが可能になります。

※参考:ApplicationSet Controller - Use Cases

ApplicationSetを動かしてみる

ここから実際にApplicationSetを動かしてみます。今回はひとまず動かしてみるという目的で、いくつかのパターンで動かしました。

基本的にはドキュメント通りに動かすだけですが、1点だけ気を付けるとすると、ApplicationSet Controllerと同じNamespaceに ApplicationSetリソースを作成する必要があります。

※参考:ApplicationSet Controller - How ApplicationSet controller interacts with Argo CD

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

インストール

ApplicationSetはArgoCDと一緒に利用する必要があります。今回はArgoCDとApplicationSetを別々に導入しました。ArgoCDの導入はこちらのページに記載されているので割愛します。ArgoCD導入後は、ArgoCDと同じNamespace(今回は argocd )にApplicationSet Controllerなどのリソースをデプロイすれば、準備は完了です。

kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj-labs/applicationset/v0.2.0/manifests/install.yaml

※参考:ApplicationSet Controller - Getting Started

Local cluster

まずはArgoCDの導入されたクラスター上に、ApplicationSetを使ってリソースを作成します。

List generator

使ったマニフェストファイルはこちらです。 spec.generator.list 部分にクラスターの名称とURLを指定します。そうすると spec.template 配下にある {{cluster}} {{url}} 部分にパラメータが設定され、それぞれのパラメータに応じた Applicationリソースが作成されます。

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: guestbook
  namespace: argocd
spec:
  generators:
  - list:
      elements:
      - cluster: in-cluster
        url: https://kubernetes.default.svc
  template:
    metadata:
      name: '{{cluster}}-guestbook'
    spec:
      project: default
      source:
        repoURL: https://github.com/argoproj/argocd-example-apps.git
        targetRevision: HEAD
        path: guestbook
      destination:
        server: '{{url}}'
        namespace: default

※ 参考:ApplicationSet Controller - List Generator

上記ファイルをデプロイすると、以下のようにリソースが作成されます。

$ kubectl apply -f list-applicationset.yaml 
applicationset.argoproj.io/guestbook created

$ kubectl get applicationset -n argocd
NAME        AGE
guestbook   16s


$ kubectl get app -n argocd
NAME                   SYNC STATUS   HEALTH STATUS
in-cluster-guestbook   OutOfSync     Missing

$ argocd app list
NAME                  CLUSTER                         NAMESPACE  PROJECT  STATUS     HEALTH   SYNCPOLICY  CONDITIONS  REPO                                                 PATH       TARGET
in-cluster-guestbook  https://kubernetes.default.svc  argocd     default  OutOfSync  Missing  <none>      <none>      https://github.com/argoproj/argocd-example-apps.git  guestbook  HEAD  

あとは argocd app sync コマンドなどでSyncすれば、テスト用のアプリケーションがデプロイされます。もちろん templatespec.syncPolicy: automated を追加すればして自動Syncを有効にすれば、ApplicationSetデプロイ後にテスト用アプリのリソースは作られます。

なお、ApplicationSet controllerのログは以下の通り。

ApplicationSet作成時にControllerに出力されるログ

$ kubectl logs -n argocd argocd-applicationset-controller-5558c458d5-n52d9 -f
2021-11-23T01:58:32.411Z        INFO    setup   ApplicationSet controller v0.2.0 using namespace 'argocd'       {"namespace": "argocd", "COMMIT_ID": "2fb043581b8692c84205075494acab6f4b5aef2d"}
2021-11-23T01:58:33.118Z        INFO    controller-runtime.metrics      metrics server is starting to listen    {"addr": ":8080"}
2021-11-23T01:58:33.119Z        INFO    setup   Starting manager
2021-11-23T01:58:33.219Z        INFO    controller-runtime.manager.controller.applicationset    Starting EventSource   {"reconciler group": "argoproj.io", "reconciler kind": "ApplicationSet", "source": "kind source: /, Kind="}
2021-11-23T01:58:33.220Z        INFO    controller-runtime.manager.controller.applicationset    Starting EventSource   {"reconciler group": "argoproj.io", "reconciler kind": "ApplicationSet", "source": "kind source: /, Kind="}
2021-11-23T01:58:33.220Z        INFO    controller-runtime.manager.controller.applicationset    Starting EventSource   {"reconciler group": "argoproj.io", "reconciler kind": "ApplicationSet", "source": "kind source: /, Kind="}
2021-11-23T01:58:33.220Z        INFO    controller-runtime.manager.controller.applicationset    Starting Controller    {"reconciler group": "argoproj.io", "reconciler kind": "ApplicationSet"}
2021-11-23T01:58:33.219Z        INFO    controller-runtime.manager      starting metrics server {"path": "/metrics"}
2021-11-23T01:58:33.421Z        INFO    controller-runtime.manager.controller.applicationset    Starting workers       {"reconciler group": "argoproj.io", "reconciler kind": "ApplicationSet", "worker count": 1}




# ApplicationSet作成時
time="2021-11-23T02:08:33Z" level=info msg="Alloc=6126 TotalAlloc=18354 Sys=73297 NumGC=9 Goroutines=49"
time="2021-11-23T02:11:09Z" level=info msg="generated 1 applications" generator="{0xc0002b3080 <nil> <nil> <nil> <nil> <nil>}"
time="2021-11-23T02:11:09Z" level=info msg="Starting configmap/secret informers"
time="2021-11-23T02:11:09Z" level=info msg="Configmap/secret informer synced"
time="2021-11-23T02:11:09Z" level=info msg="created Application" app=in-cluster-guestbook appSet=guestbook
2021-11-23T02:11:09.581Z        DEBUG   controller-runtime.manager.events       Normal  {"object": {"kind":"ApplicationSet","namespace":"argocd","name":"guestbook","uid":"35d9488c-837a-46c0-a029-014890b804f9","apiVersion":"argoproj.io/v1alpha1","resourceVersion":"1618215"}, "reason": "created", "message": "created Application \"in-cluster-guestbook\""}
time="2021-11-23T02:11:09Z" level=info msg="end reconcile" requeueAfter=0s
time="2021-11-23T02:11:09Z" level=info msg="generated 1 applications" generator="{0xc00092cdc0 <nil> <nil> <nil> <nil> <nil>}"
time="2021-11-23T02:11:09Z" level=info msg="unchanged Application" app=in-cluster-guestbook appSet=guestbook
2021-11-23T02:11:09.607Z        DEBUG   controller-runtime.manager.events       Normal  {"object": {"kind":"ApplicationSet","namespace":"argocd","name":"guestbook","uid":"35d9488c-837a-46c0-a029-014890b804f9","apiVersion":"argoproj.io/v1alpha1","resourceVersion":"1618215"}, "reason": "unchanged", "message": "unchanged Application \"in-cluster-guestbook\""}
time="2021-11-23T02:11:09Z" level=info msg="end reconcile" requeueAfter=0s
time="2021-11-23T02:11:11Z" level=info msg="generated 1 applications" generator="{0xc0007f22c0 <nil> <nil> <nil> <nil> <nil>}"
time="2021-11-23T02:11:11Z" level=info msg="unchanged Application" app=in-cluster-guestbook appSet=guestbook
2021-11-23T02:11:11.713Z        DEBUG   controller-runtime.manager.events       Normal  {"object": {"kind":"ApplicationSet","namespace":"argocd","name":"guestbook","uid":"35d9488c-837a-46c0-a029-014890b804f9","apiVersion":"argoproj.io/v1alpha1","resourceVersion":"1618215"}, "reason": "unchanged", "message": "unchanged Application \"in-cluster-guestbook\""}
time="2021-11-23T02:11:11Z" level=info msg="end reconcile" requeueAfter=0s

Cluster generator

マニフェストファイルは以下の通りです。

Cluster generatorはArgoCDに登録されているクラスターを検知してパラメータを生成するため、デプロイ先のクラスターを指定しない場合は特に設定は必要ありません。 spec.template{{name}} {{server}} には、ArgoCDで登録されているクラスター情報がそのまま使われます(ここではそれぞれ in-cluster https://kubernetes.default.svc)。

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: guestbook
  namespace: argocd
spec:
  generators:
  - clusters: {} 
  template:
    metadata:
      name: '{{name}}-guestbook'
    spec:
      project: "default"
      source:
        repoURL: https://github.com/argoproj/argocd-example-apps/
        targetRevision: HEAD
        path: guestbook
      destination:
        server: '{{server}}'
        namespace: default

※ 参考:ApplicationSet Controller - Cluster Generator

こちらもデプロイすれば、先ほどと同様リソースが作成されます。

$ kubectl apply -f cluster-applicationset.yaml 
applicationset.argoproj.io/guestbook created

$ kubectl get appset -n argocd
NAME        AGE
guestbook   8s

$ kubectl get apps -n argocd
NAME                   SYNC STATUS   HEALTH STATUS
in-cluster-guestbook   OutOfSync     Missing

あとはSyncすればPodも作成されます。

$ argocd app sync in-cluster-guestbook
$ kubectl get pods 
NAME                            READY   STATUS    RESTARTS   AGE
guestbook-ui-85985d774c-t7mxr   1/1     Running   0          13s

なお、ApplicationSetを削除すると、関連するApplication / Podなどのリソースがすべて削除されます。

$ kubectl delete -f cluster-applicationset.yaml 
applicationset.argoproj.io "guestbook" deleted

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

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

$ kubectl get pods
No resources found in default namespace.

Git generator

マニフェストファイルはこちらです。

ここでは directories fieldで使用するマニフェストファイルの場所を指定します。また template にある path.basename には、path に指定したディレクトリの配下にあるサブディレクトリの名称が入ります(ここでは argo-workflows prometheus-operator)。

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: cluster-addons
  namespace: argocd
spec:
  generators:
  - git:
      repoURL: https://github.com/argoproj-labs/applicationset.git
      revision: HEAD
      directories:
      - path: examples/git-generator-directory/cluster-addons/*
  template:
    metadata:
      name: '{{path.basename}}'
    spec:
      project: default
      source:
        repoURL: https://github.com/argoproj-labs/applicationset.git
        targetRevision: HEAD
        path: '{{path}}'
      destination:
        server: https://kubernetes.default.svc
        namespace: '{{path.basename}}'

※ 参考:ApplicationSet Controller - Git Generator

こちらも同様にデプロイすれば、以下のようにリソースは作成されます。ここではadd-onをデプロイするためにリポジトリを変更しており、2つのApplicationリソースが作成されます。

$ kubectl apply -f git-applicationset.yaml 
applicationset.argoproj.io/cluster-addons created

$ kubectl get appset -n argocd
NAME             AGE
cluster-addons   25s

$ kubectl get app -n argocd
NAME                  SYNC STATUS   HEALTH STATUS
argo-workflows        OutOfSync     Missing
prometheus-operator   OutOfSync     Missing

こちらもSyncすればリソースは作成されますが、今回はNamespaceがサブディレクトリ名と一致する形式のため、事前にNamespaceを作成してからSyncします。

$ kubectl create ns argo-workflows

$ argocd app sync argo-workflows

$ kubectl get pods -n argo-workflows
NAME                                   READY   STATUS    RESTARTS   AGE
argo-server-5ddd56c954-mkgkg           1/1     Running   0          31s
workflow-controller-574596cc9f-2wzmk   1/1     Running   0          31s

External cluster

次は外部クラスターに向けてのデプロイです。今回は eksctl コマンドで別のEKSクラスターを作成し、その後ArgoCDにクラスターを追加登録します。

# external clusterの作成
$ eksctl create cluster -f eks-clusterconfig-2.yaml

$ eksctl get clusters
2021-11-23 15:22:29 []  eksctl version 0.69.0
2021-11-23 15:22:29 []  using region ap-northeast-1
NAME                    REGION          EKSCTL CREATED
eks-cluster-argodst     ap-northeast-1  True #追加したクラスター
eks-cluster-argosrc     ap-northeast-1  True

# 登録するクラスターの確認
$ kubectl config get-contexts

# クラスターの登録
$ argocd cluster add <external cluster name>

※ 参考:ArgoCD Docs - Argocd cluster add

クラスターを登録すると以下のようにSecretリソースが作成されます。なおSecretリソース名にあるURL先のクラスターは、既に削除されています。

$ kubectl get secret -n argocd
NAME                                                                                       TYPE                                  DATA   AGE
argocd-application-controller-token-22xzv                                                  kubernetes.io/service-account-token   3      4h39m
argocd-applicationset-controller-token-v84pl                                               kubernetes.io/service-account-token   3      4h29m
argocd-dex-server-token-g76wx                                                              kubernetes.io/service-account-token   3      4h39m
argocd-initial-admin-secret                                                                Opaque                                1      4h38m
argocd-redis-token-m94v5                                                                   kubernetes.io/service-account-token   3      4h39m
argocd-secret                                                                              Opaque                                5      4h39m
argocd-server-token-rqn9r                                                                  kubernetes.io/service-account-token   3      4h39m
# "cluster-"で始まるSecretが作成される
cluster-c68e805bffb0dd155295b27b15cbf8ac.gr7.ap-northeast-1.eks.amazonaws.com-3093402059   Opaque                                3      90s
default-token-qc648                                                                        kubernetes.io/service-account-token   3      4h39m

List generator

ここでは以下のマニフェストファイルを使用します。これは先ほどのマニフェストファイルに外部クラスター情報を追加したものです。

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: guestbook
  namespace: argocd
spec:
  generators:
  - list:
      elements:
      - cluster: in-cluster
        url: https://kubernetes.default.svc
      # 追加
      - cluster: external-cluster
        url: https://C68E805BFFB0DD155295B27B15CBF8AC.gr7.ap-northeast-1.eks.amazonaws.com
  template:
    metadata:
      name: '{{cluster}}-guestbook'
    spec:
      project: default
      source:
        repoURL: https://github.com/argoproj/argocd-example-apps.git
        targetRevision: HEAD
        path: guestbook
      destination:
        server: '{{url}}'
        namespace: default

こちらをデプロイするとApplicationが作成されます。今回は2つのクラスターに対してApplication リソースが作られ、その名称はクラスターと対応しています。

$ kubectl apply -f list-applicationset.yaml 
applicationset.argoproj.io/guestbook created

$ kubectl get appset -n argocd
NAME        AGE
guestbook   14s

$ kubectl get app -n argocd
NAME                         SYNC STATUS   HEALTH STATUS
external-cluster-guestbook   OutOfSync     Missing
in-cluster-guestbook         OutOfSync     Missing

あとはSyncすればデプロイは完了です。

$ argocd app sync external-cluster-guestbook

$ argocd app list
NAME                        CLUSTER                                                                        NAMESPACE  PROJECT  STATUS     HEALTH   SYNCPOLICY  CONDITIONS  REPO
                                    PATH       TARGET
external-cluster-guestbook  https://C68E805BFFB0DD155295B27B15CBF8AC.gr7.ap-northeast-1.eks.amazonaws.com  default    default  Synced     Healthy  <none>      <none>      https://github.com/argoproj/argocd-example-apps.git  guestbook  HEAD
in-cluster-guestbook        https://kubernetes.default.svc                                                 default    default  OutOfSync  Missing  <none>      <none>      https://github.com/argoproj/argocd-example-apps.git  guestbook  HEAD

# 操作対象のクラスターを切り替える
$ kubectl config use-context <external cluster name>

$ kubectl get pods
NAME                            READY   STATUS    RESTARTS   AGE
guestbook-ui-85985d774c-v7rv6   1/1     Running   0          4m34s

Cluster generator

ここでは外部クラスターのみにデプロイするよう、条件を追加しています。ArgoCDにクラスターを登録した際に作られるSecretには argocd.argoproj.io/secret-type=cluster というタグが付与されており、これを含むクラスターだけを対象とします。

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: guestbook
  namespace: argocd
spec:
  generators:
  - clusters:
    # 追加
      selector:
        matchLabels:
          argocd.argoproj.io/secret-type: cluster
  template:
    metadata:
      name: 'external-guestbook'
    spec:
      project: "default"
      source:
        repoURL: https://github.com/argoproj/argocd-example-apps/
        targetRevision: HEAD
        path: guestbook
      destination:
        server: '{{server}}'
        namespace: default

なお spec.template.metadata.name の名称を {{name}}-guestbook から external-guestbook に変更していますが、クラスターの名称が長すぎるとSync時に以下のようなエラーが発生するため変更しています。

# エラーの例
$ argocd app sync 

(中略)

Deployment.apps "guestbook-ui" is invalid: metadata.labels: Invalid value: "<cluster name>": must be no more than 63 characters

デプロイ後は以下の通りです。

$ kubectl apply -f cluster-applicationset.yaml

$ argocd app sync external-guestbook

$ kubectl config use-context <external cluster>

$ kubectl get pods
NAME                            READY   STATUS    RESTARTS   AGE
guestbook-ui-85985d774c-v4xqc   1/1     Running   0          36s

Matrix generator

Matrix generatorは2種類のgeneratorを組み合わせるgeneratorです。今回は Cluster generator と Git generator を組み合わせたパターンにしています。

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: cluster-git
  namespace: argocd
spec:
  generators:
    - matrix:
        generators:
          - git:
              repoURL: https://github.com/argoproj-labs/applicationset.git
              revision: HEAD
              directories:
              - path: examples/matrix/cluster-addons/*
          - clusters:
              selector:
                matchLabels:
                  argocd.argoproj.io/secret-type: cluster
  template:
    metadata:
      name: '{{path.basename}}-external'
    spec:
      project: default
      source:
        repoURL: https://github.com/argoproj-labs/applicationset.git
        targetRevision: HEAD
        path: '{{path}}'
      destination:
        server: '{{server}}'
        namespace: default

※ 参考:ApplicationSet Controller - Matrix Generator

デプロイ後は以下の通りです。

$ kubectl apply -f matrix-applicationset.yaml
applicationset.argoproj.io/cluster-git created

$ kubectl get appset -n argocd
NAME          AGE
cluster-git   11s

$ kubectl get app -n argocd
NAME                           SYNC STATUS   HEALTH STATUS
argo-workflows-external        OutOfSync     Missing
prometheus-operator-external   OutOfSync     Missing

Flaggerに入門する

今回はProgressive Deliveryを実現するツールの一つであるFlaggerを試してみます。なお、Progressive Deliveryについては、以下の参考リンクなどを参照ください。

※参考リンク:

Flaggerとは

Flaggerは Progressive Dlivery Operator for Kubernetes と打ち出されている通り、KubernetesをベースにProgressive Deliveryの実現をサポートするプロダクトです。

Flaggerを利用する環境は、以下のような構成となります。

※画像:Flagger Docより

flagger

Flaggerの実現するのは大きく3つ紹介されており、 Progressive Deliveryによる「より安全なリリース」 サービスメッシュやIngress Controllerによる「柔軟なTraffic Routing」 Progressive Deliveryに用いるメトリックをカスタムすることも可能な「拡張性のあるValidation」 となります。

具体的な機能としては、以下のようなものが挙げられます。

サービスメッシュ・Ingress Controllerと組み合わせたトラフィックコントロール

FlaggerはKubernetes CNIと組み合わせることで、単体でもトラフィックの切り替えを行うことができますが、Istio/Linkerdといったサービスメッシュ、Nginx/ContourなどのIngress Controllerとして機能するプロダクトと組み合わせることで、CanaryリリースやA/Bテストなど、より発展的なデリバリーを実現します。

GitOpsツールとの組み合わせ

FlaggerはGitOpsプロダクトと組み合わせることで、GitOps + Progressive Deliveryというデプロイ・リリースパイプラインを実現します。以前動かしてみたFluxなどと組み合わせることが可能です

4種類のデプロイ・デリバリー戦略に対応

Flaggerは Canary というCustom Resourceの設定を変更することで、4種類のデプロイ・デリバリー戦略に対応しています。

  • Canary Release: HTTPリクエスト成功率などの重要な指標(KPI、Key Performace Indicator)を計測しつつ、異なるバージョンのアプリケーションへのトラフィック量を少しずつ変更します。
  • A/B Testing: Session Affinity (Sticky Session)を要求するようなフロントエンドのアプリケーションに対応するため、Canary ReleaseにHTTPヘッダーやCookieの条件一致を加えることで、同じユーザーが同一のバージョンにアクセスし続けるようトラフィックを制御し、A/Bテストを実現します。
  • Blue/Green: KPIなどをベースに新しいバージョンのアプリケーションに問題がないと判断できたら、一気にトラフィックを新しいバージョンのほうへ切り替えます。
  • Blue/Green with Traffic Mirroring: Canary ReleaseやBlue/Greenの前段階として利用します。インバウンドな通信をコピーして、一方は現在稼働中のServiceに、もう一方をCanary Serviceへ転送します。Canary Serviceからのレスポンスは破棄しますが、メトリックは収集し、Canaryのほうが問題ない状態かを確認することができます。

※参考リンク:Flagger Doc - Deployment Strategies

モニタリングツールへのクエリ

Flaggerはメトリックをベースにして、可用性やエラー率などのSLOを評価します。Flaggerは HTTPリクエスト成功率Duration を組み込みのメトリックとして持ちますが、カスタムすることも可能です。メトリックのターゲットにはPrometheusやDataDogを利用できます。

※参考リンク:Flagger Doc - Metrics Analysis

WebhookによるAnalysisの拡張

Flaggerでは、アプリケーション切替の前後に行うテスト(Analysis)を、Webhookによって拡張することができます。Flaggerでは8種類のHookが用意されており、そのレスポンスコードによって切り替えが発生します。またFlaggerではWebhhokの設定でコマンドを指定することで、切り替え前後に負荷テストなどを実施できます。

※参考リンク:Flagger Doc - Webhooks

通知サービスへのアラート

FlaggerはSlackやMicrosoft Teamsなどの各種チャットツールへアラートを送信することができます。送信先のプロバイダーは AlertProvider というCustom Resourceで定義します。

※参考リンク:Flagger Doc - Alerting

Flaggerを動かしてみる

ここから実際にFlaggerを動かします。Flaggerのドキュメントには、各デプロイ戦略とツールとの組み合わせごとにチュートリアルが用意されています。今回はIstioをベースに2種類のデプロイ戦略をなぞってみます。Istioについては、以前の投稿などをご参照ください。環境の用意はこちらのドキュメントをベースにしています。

※参考リンク:

前提条件

Flaggerを利用する前提条件は以下の通りです。

  • Kubernetes version: 1.16 以上
  • istio version: 1.5 以上

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

# eksctl version
$ eksctl version
0.57.0

# Kubernetes / kubectl version
$ kubectl version
Client Version: version.Info{Major:"1", Minor:"20", GitVersion:"v1.20.0", GitCommit:"af46c47ce925f4c4ad5cc8d1fca46c7b77d13b38", GitTreeState:"clean", BuildDate:"2020-12-08T17:59:43Z", GoVersion:"go1.15.5", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"20+", GitVersion:"v1.20.7-eks-8be107", GitCommit:"8be107e517a77bde856aced857f3428e4f344969", GitTreeState:"clean", BuildDate:"2021-06-12T03:14:13Z", GoVersion:"go1.15.12", Compiler:"gc", Platform:"linux/amd64"}

# Istio version
$ istioctl version
no running Istio pods in "istio-system"
1.10.3

Istio / Prometheus / Flaggerのデプロイ

まずはIstioのデプロイを行います。Istioは istioctl コマンドからデプロイを行います。今回使用しているProfileはこちらから確認できます。なお、Istioインストールの際、istiodに要求されるメモリ量が多いため、nodeのスペックは十分大きいものを指定したほうが良いでしょう。今回はEC2インスタンスのsペックは t3.large を指定しています。

$ istioctl manifest install --set profile=default
This will install the Istio 1.10.3 default profile with ["Istio core" "Istiod" "Ingress gateways"] components into the cluster. Proceed? (y/N) y
✔ Istio core installed                                                                                                 
✔ Istiod installed                                                                                                     
✔ Ingress gateways installed                                                                                           
✔ Installation complete     


# デプロイ後の確認
$ kubectl get pods -n istio-system
NAME                                   READY   STATUS    RESTARTS   AGE
istio-ingressgateway-c4d8648bc-pkm72   1/1     Running   0          59s
istiod-7bd65bd549-76xgz                1/1     Running   0          71s


$ kubectl get svc -n istio-system
NAME                   TYPE           CLUSTER-IP       EXTERNAL-IP                                                                    PORT(S)                                      AGE
istio-ingressgateway   LoadBalancer   10.100.230.248   a50db11ad30db428b926a77884d22fa1-1849188677.ap-northeast-1.elb.amazonaws.com   15021:30212/TCP,80:31556/TCP,443:32089/TCP   96s
istiod                 ClusterIP      10.100.89.234    <none>                                                                         15010/TCP,15012/TCP,443/TCP,15014/TCP        108s

次にPrometheusをデプロイします。

$ kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.10/samples/addons/prometheus.yaml
serviceaccount/prometheus created
configmap/prometheus created
clusterrole.rbac.authorization.k8s.io/prometheus created
clusterrolebinding.rbac.authorization.k8s.io/prometheus created
service/prometheus created
deployment.apps/prometheus created


# デプロイ後の確認
$ kubectl get pods -n istio-system
NAME                                   READY   STATUS    RESTARTS   AGE
istio-ingressgateway-c4d8648bc-pkm72   1/1     Running   0          3m19s
istiod-7bd65bd549-76xgz                1/1     Running   0          3m31s
prometheus-69f7f4d689-bz2z5            2/2     Running   0          15s

$ kubectl get svc -n istio-system
NAME                   TYPE           CLUSTER-IP       EXTERNAL-IP                                                                    PORT(S)                                      AGE
istio-ingressgateway   LoadBalancer   10.100.230.248   a50db11ad30db428b926a77884d22fa1-1849188677.ap-northeast-1.elb.amazonaws.com   15021:30212/TCP,80:31556/TCP,443:32089/TCP   3m32s
istiod                 ClusterIP      10.100.89.234    <none>                                                                         15010/TCP,15012/TCP,443/TCP,15014/TCP        3m44s
prometheus             ClusterIP      10.100.238.59    <none>                                                                         9090/TCP                                     28s

最後にFlaggerをデプロイします。

$ kubectl apply -k github.com/fluxcd/flagger//kustomize/istio
customresourcedefinition.apiextensions.k8s.io/alertproviders.flagger.app created
customresourcedefinition.apiextensions.k8s.io/canaries.flagger.app created
customresourcedefinition.apiextensions.k8s.io/metrictemplates.flagger.app created
serviceaccount/flagger created
clusterrole.rbac.authorization.k8s.io/flagger created
clusterrolebinding.rbac.authorization.k8s.io/flagger created
deployment.apps/flagger created


# デプロイ後の確認
$ kubectl get pods -n istio-system
NAME                                   READY   STATUS    RESTARTS   AGE
flagger-5cf787b9c8-jgdtv               1/1     Running   0          44s
istio-ingressgateway-c4d8648bc-pkm72   1/1     Running   0          6m15s
istiod-7bd65bd549-76xgz                1/1     Running   0          6m27s
prometheus-69f7f4d689-bz2z5            2/2     Running   0          3m11s

Canary Deployment

まずはCanary Deploymentを試します。手順はこちらのドキュメントに記載されています。

前準備

まず使用するデモ用アプリが、サービスメッシュの外からアクセスできるよう Gateway リソースをデプロイします。


ingress-gateway.yaml

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: public-gateway
  namespace: istio-system
spec:
  selector:
    istio: ingressgateway
  servers:
    - port:
        number: 80
        name: http
        protocol: HTTP
      hosts:
        - "*"


$ kubectl apply -f ingress-gateway.yaml
gateway.networking.istio.io/public-gateway created


# デプロイ後の確認
$ kubectl get gateway -n istio-system
NAME             AGE
public-gateway   9s

次にテスト用アプリを起動するNamespaceの作成と、IstioによるSidecar Proxyの注入を行うためのラベリングを行います。

$ kubectl create ns test
namespace/test created

$ kubectl label namespace test istio-injection=enabled
namespace/test labeled

# デプロイ後の確認
$ kubectl get ns --show-labels
NAME              STATUS   AGE   LABELS
default           Active   28m   <none>
istio-system      Active   13m   <none>
kube-node-lease   Active   28m   <none>
kube-public       Active   28m   <none>
kube-system       Active   28m   <none>
test              Active   82s   istio-injection=enabled

次にテスト用のアプリとHPAをデプロイします。なお、今回の手順ではHPAは特に利用しないため、 metrics-server のインストールは実施しません。そのためHPAのターゲットも Unknown 状態となります。

$ kubectl apply -k https://github.com/fluxcd/flagger//kustomize/podinfo?ref=main
deployment.apps/podinfo created
horizontalpodautoscaler.autoscaling/podinfo created


# デプロイ後の確認
$ kubectl get pods -n test
NAME                      READY   STATUS    RESTARTS   AGE
podinfo-99dc84b6f-bmtvv   2/2     Running   0          56s
podinfo-99dc84b6f-l5pk8   2/2     Running   0          71s

$ kubectl get hpa -n test
NAME      REFERENCE            TARGETS         MINPODS   MAXPODS   REPLICAS   AGE
podinfo   Deployment/podinfo   <unknown>/99%   2         4         2          98s

また、Progressive DeliveryでのAnalysisで利用する loadtester Podも合わせてデプロイします。この loadtesterrakyll/hey / bojand/ghz という2つの負荷テストツールをベースとしています。

$ kubectl apply -k https://github.com/fluxcd/flagger//kustomize/tester?ref=main
service/flagger-loadtester created
deployment.apps/flagger-loadtester created


# デプロイ後の確認
$ kubectl get pods -n test
NAME                                  READY   STATUS    RESTARTS   AGE
flagger-loadtester-64695f854f-6mkgj   2/2     Running   0          29s
podinfo-99dc84b6f-bmtvv               2/2     Running   0          4m25s
podinfo-99dc84b6f-l5pk8               2/2     Running   0          4m40s

$ kubectl get svc -n test
NAME                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
flagger-loadtester   ClusterIP   10.100.21.132   <none>        80/TCP    25s

Canary リソースのデプロイ

Flaggerは Canary というCustom Resourceを利用し、既存のDeploymentに対して、指定したデプロイ戦略を実行します。どのデプロイ戦略を利用するかは、 Canary 中に指定したパラメータによって変更します。

今回利用する Canaryマニフェストファイルは以下の通りです。少しだけパラメータの説明を付け加えます。

  • spec.targetRef: ターゲットとするリソース種別を指定。 Deployment DaemonSet のいずれかを指定可能
  • spec.progressDeadlineSeconds: Canary Deployment実行時、ロールバック前に進行する際の最大秒数。 kubectl get canary 実行時にCanary Deploymentの進行を表示する間隔。
  • spec.analysis: Progressive DeliveryのAnalysisを設定する部分。
    • interval: analysisの実行間隔
    • threshold: ロールバックを実行する基準となる、analysisの失敗回数
    • maxWeight: Canaryへ転送する最大のトラフィック転送割合
    • stepWeight: Canaryへの転送量を1回あたりに増やす割合。
    • metrics: メトリックの設定箇所
      • thresholdRange: request-success-rate の場合はリクエスト成功率の最低値、 request-duration の場合はリクエストのP99 duration最大ミリセカンド値を指定
    • webhooks: Webhookの設定

podinfo-canary.yaml

apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
  name: podinfo
  namespace: test
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: podinfo
  progressDeadlineSeconds: 60
  autoscalerRef:
    apiVersion: autoscaling/v2beta2
    kind: HorizontalPodAutoscaler
    name: podinfo
  service:
    port: 9898
    targetPort: 9898
    gateways:
    - public-gateway.istio-system.svc.cluster.local
    hosts:
    - app.example.com
    trafficPolicy:
      tls:
        mode: DISABLE
    retries:
      attempts: 3
      perTryTimeout: 1s
      retryOn: "gateway-error,connect-failure,refused-stream"
  analysis:
    interval: 1m
    threshold: 5
    maxWeight: 50
    stepWeight: 10
    metrics:
    - name: request-success-rate
      thresholdRange:
        min: 99
      interval: 1m
    - name: request-duration
      thresholdRange:
        max: 500
      interval: 30s
    webhooks:
      - name: acceptance-test
        type: pre-rollout
        url: http://flagger-loadtester.test/
        timeout: 30s
        metadata:
          type: bash
          cmd: "curl -sd 'test' http://podinfo-canary:9898/token | grep token"
      - name: load-test
        url: http://flagger-loadtester.test/
        timeout: 5s
        metadata:
          cmd: "hey -z 1m -q 10 -c 2 http://podinfo-canary.test:9898/"


上記マニフェストファイルをデプロイすると、先ほど作成した podinfo Podを置き換える形で podinfo-primary というPodが作成されます。またPod以外にも以下のリソースが作成されます。

  • Deployment / Pod: podinfo-primary
  • HPA: podinfo-primary
  • Service: podinfo podinfo-canary podinfo-primary
  • Destination Rule: podinfo-canary podinfo-primary
  • Virtual Service: podinfo
$ kubectl apply -f podinfo-canary.yaml
canary.flagger.app/podinfo created


# デプロイ後の確認
$ kubectl get canary -n test
NAME      STATUS        WEIGHT   LASTTRANSITIONTIME
podinfo   Initialized   0        2021-07-21T10:02:46Z

$ kubectl get pods -n test
NAME                                  READY   STATUS    RESTARTS   AGE
flagger-loadtester-64695f854f-6mkgj   2/2     Running   0          30m
podinfo-primary-657f8c97dd-28lbv      2/2     Running   0          2m31s
podinfo-primary-657f8c97dd-z7sx6      2/2     Running   0          2m31s

$ kubectl get hpa -n test
NAME              REFERENCE                    TARGETS         MINPODS   MAXPODS   REPLICAS   AGE
podinfo           Deployment/podinfo           <unknown>/99%   2         4         0          34m
podinfo-primary   Deployment/podinfo-primary   <unknown>/99%   2         4         2          70s

$ kubectl get service -n test
NAME                 TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
flagger-loadtester   ClusterIP   10.100.21.132    <none>        80/TCP     29m
podinfo              ClusterIP   10.100.172.155   <none>        9898/TCP   9s
podinfo-canary       ClusterIP   10.100.249.86    <none>        9898/TCP   69s
podinfo-primary      ClusterIP   10.100.129.195   <none>        9898/TCP   69s

$ kubectl get destinationrule -n test
NAME              HOST              AGE
podinfo-canary    podinfo-canary    57s
podinfo-primary   podinfo-primary   57s

$ kubectl get virtualservice -n test
NAME      GATEWAYS                                            HOSTS                           AGE
podinfo   ["public-gateway.istio-system.svc.cluster.local"]   ["app.example.com","podinfo"]   43s

またこの時 Canary リソースのログを見ると、Initializationのプロセスが確認できます。

$ kubectl describe canary podinfo -n test

(中略)

Events:
  Type     Reason  Age                    From     Message
  ----     ------  ----                   ----     -------
  Warning  Synced  4m41s                  flagger  podinfo-primary.test not ready: waiting for rollout to finish: observed deployment generation less then desired generation
  Normal   Synced  3m41s (x2 over 4m41s)  flagger  all the metrics providers are available!
  Normal   Synced  3m41s                  flagger  Initialization done! podinfo.test

これでProgressive Deliveryを実行する準備ができました。

アプリケーションのアップデート

ここからCanary Deploymentを実行します。デプロイした podinfoのイメージを更新することでアップデートを開始すると、Analysisを実行した後にPodのアップデートを行います。

Canary Deploymentの場合は以下のような構成となります。

※画像: Flagger Docより

canary-deployment

Flaggerでは Primary Canary という2種類のPodを利用します。Canary PodはAnalysisを実行する対象のPodで、アップデート中に作成し、完了後に削除されます。 Primary Podは実際にトラフィックを流す対象のPodで、Analysisをクリアすると Primary Podをバージョンアップすることで、アップデートが完了します。

Canary Deployment実行時のFlaggerの処理は、以下のように進行します。基本的な流れはCanary DeploymentもBlue/Green Deploymentも共通です。

  • Progressing フェーズ: Analysisの実行
    • 新しいイメージバージョンのCanary Podを作成
    • Canary Podへトラフィックを転送する。この時メトリックも収集し、評価する
    • Canary Podへの転送量を maxWeight まで増加する
  • Promoting フェーズ: Analysisの完了後、Primaryのスペックを更新
    • 新しいイメージタグのPrimary Podを作成する(Secret等のリソースがある場合はそれも複製する)
    • 古いPrimary Podを削除する
  • Finalising フェーズ: トラフィックをPrimary側へ切り替える
  • Succeeded フェーズ: Analysisとアップデートの完了
    • Canary Podを削除する

※参考リンク:GitHub - flux/flagger: status.go

アップデート前のイメージタグは 3.1.0 です。

$ kubectl describe pod podinfo-primary-657f8c97dd-28lbv -n test | grep 'Image:'
    Image:         docker.io/istio/proxyv2:1.10.3
    Image:         stefanprodan/podinfo:3.1.0
    Image:         docker.io/istio/proxyv2:1.10.3

アップデート実行前に、PodとCanaryのウォッチを開始しておきます。

# watch
$ kubectl get canary -n test -w
$ kubectl get pods -n test -w

# イメージアップデート
kubectl -n test set image deployment/podinfo podinfod=stefanprodan/podinfo:3.1.1

ここからフェーズごとの推移を載せておきます。

Progressing

# Progressing
## Weightが徐々に増加する
$ kubectl get canary -n test -w
NAME      STATUS        WEIGHT   LASTTRANSITIONTIME
podinfo   Initialized   0        2021-07-21T10:02:46Z
podinfo   Progressing   0        2021-07-21T10:13:46Z
podinfo   Progressing   10       2021-07-21T10:14:46Z
podinfo   Progressing   20       2021-07-21T10:15:46Z
podinfo   Progressing   30       2021-07-21T10:16:46Z
podinfo   Progressing   40       2021-07-21T10:17:46Z
podinfo   Progressing   50       2021-07-21T10:18:46Z


## Canary Podが作成される
$ kubectl get pods -n test -w
NAME                                  READY   STATUS    RESTARTS   AGE
flagger-loadtester-64695f854f-6mkgj   2/2     Running   0          39m
podinfo-primary-657f8c97dd-28lbv      2/2     Running   0          11m
podinfo-primary-657f8c97dd-z7sx6      2/2     Running   0          11m


podinfo-5f8fd4f546-vtjvl              0/2     Pending   0          0s
podinfo-5f8fd4f546-vtjvl              0/2     Pending   0          0s
podinfo-5f8fd4f546-vtjvl              0/2     Init:0/1   0          0s
podinfo-5f8fd4f546-vtjvl              0/2     PodInitializing   0          1s
podinfo-5f8fd4f546-6mqz8              0/2     Pending           0          0s
podinfo-5f8fd4f546-6mqz8              0/2     Pending           0          0s
podinfo-5f8fd4f546-6mqz8              0/2     Init:0/1          0          0s
podinfo-5f8fd4f546-6mqz8              0/2     Init:0/1          0          1s
podinfo-5f8fd4f546-6mqz8              0/2     PodInitializing   0          2s
podinfo-5f8fd4f546-vtjvl              0/2     Running           0          8s
podinfo-5f8fd4f546-vtjvl              1/2     Running           0          9s
podinfo-5f8fd4f546-6mqz8              0/2     Running           0          7s
podinfo-5f8fd4f546-6mqz8              1/2     Running           0          9s
podinfo-5f8fd4f546-vtjvl              2/2     Running           0          16s
podinfo-5f8fd4f546-6mqz8              2/2     Running           0          14s

Promoting

# Promoting
NAME      STATUS        WEIGHT   LASTTRANSITIONTIME
podinfo   Promoting     0        2021-07-21T10:19:46Z


## Primary Podを更新
NAME                                  READY   STATUS    RESTARTS   AGE
podinfo-primary-657f8c97dd-z7sx6      2/2     Terminating       0          18m
podinfo-primary-76655b74b9-cvs9z      0/2     Pending           0          0s
podinfo-primary-76655b74b9-cvs9z      0/2     Pending           0          0s
podinfo-primary-76655b74b9-cvs9z      0/2     Init:0/1          0          0s
podinfo-primary-76655b74b9-b99lq      0/2     Pending           0          0s
podinfo-primary-76655b74b9-b99lq      0/2     Pending           0          0s
podinfo-primary-76655b74b9-b99lq      0/2     Init:0/1          0          0s
podinfo-primary-76655b74b9-cvs9z      0/2     PodInitializing   0          2s
podinfo-primary-76655b74b9-b99lq      0/2     PodInitializing   0          3s
podinfo-primary-76655b74b9-cvs9z      0/2     Running           0          3s
podinfo-primary-76655b74b9-b99lq      0/2     Running           0          4s
podinfo-primary-76655b74b9-cvs9z      1/2     Running           0          4s
podinfo-primary-76655b74b9-b99lq      1/2     Running           0          5s
podinfo-primary-657f8c97dd-z7sx6      0/2     Terminating       0          18m
podinfo-primary-657f8c97dd-z7sx6      0/2     Terminating       0          18m
podinfo-primary-657f8c97dd-z7sx6      0/2     Terminating       0          18m
podinfo-primary-76655b74b9-cvs9z      2/2     Running           0          8s
podinfo-primary-76655b74b9-b99lq      2/2     Running           0          13s
podinfo-primary-657f8c97dd-28lbv      2/2     Terminating       0          18m
podinfo-primary-657f8c97dd-28lbv      0/2     Terminating       0          18m
podinfo-primary-657f8c97dd-28lbv      0/2     Terminating       0          18m
podinfo-primary-657f8c97dd-28lbv      0/2     Terminating       0          18m

Finalising Succeeded

# Finalising
NAME      STATUS        WEIGHT   LASTTRANSITIONTIME
podinfo   Finalising    0        2021-07-21T10:20:46Z

# Succeeded
NAME      STATUS        WEIGHT   LASTTRANSITIONTIME
podinfo   Succeeded     0        2021-07-21T10:21:46Z


## Canary Podの削除
NAME                                  READY   STATUS    RESTARTS   AGE
podinfo-5f8fd4f546-6mqz8              2/2     Terminating       0          7m54s
podinfo-5f8fd4f546-vtjvl              2/2     Terminating       0          8m
podinfo-5f8fd4f546-6mqz8              0/2     Terminating       0          8m
podinfo-5f8fd4f546-vtjvl              0/2     Terminating       0          8m6s
podinfo-5f8fd4f546-vtjvl              0/2     Terminating       0          8m7s
podinfo-5f8fd4f546-vtjvl              0/2     Terminating       0          8m7s
podinfo-5f8fd4f546-6mqz8              0/2     Terminating       0          8m7s
podinfo-5f8fd4f546-6mqz8              0/2     Terminating       0          8m7s

最終的には以下のような状態になります。

$ kubectl get canary -n test
NAME      STATUS      WEIGHT   LASTTRANSITIONTIME
podinfo   Succeeded   0        2021-07-21T10:21:46Z

$ kubectl get pods -n test
NAME                                  READY   STATUS    RESTARTS   AGE
flagger-loadtester-64695f854f-6mkgj   2/2     Running   0          52m
podinfo-primary-76655b74b9-b99lq      2/2     Running   0          6m32s
podinfo-primary-76655b74b9-cvs9z      2/2     Running   0          6m32s

podinfo Canaryのログは以下の通りです。

$ kubectl describe canary -n test podinfo

(中略)

Events:
  Type     Reason  Age                From     Message
  ----     ------  ----               ----     -------
  Warning  Synced  47m                flagger  podinfo-primary.test not ready: waiting for rollout to finish: observed deployment generation less then desired generation
  Normal   Synced  46m (x2 over 47m)  flagger  all the metrics providers are available!
  Normal   Synced  46m                flagger  Initialization done! podinfo.test
  Normal   Synced  35m                flagger  New revision detected! Scaling up podinfo.test
  Normal   Synced  34m                flagger  Starting canary analysis for podinfo.test
  Normal   Synced  34m                flagger  Pre-rollout check acceptance-test passed
  Normal   Synced  34m                flagger  Advance podinfo.test canary weight 10
  Normal   Synced  33m                flagger  Advance podinfo.test canary weight 20
  Normal   Synced  32m                flagger  Advance podinfo.test canary weight 30
  Normal   Synced  31m                flagger  Advance podinfo.test canary weight 40
  Normal   Synced  30m                flagger  Advance podinfo.test canary weight 50
  Normal   Synced  29m                flagger  Copying podinfo.test template spec to podinfo-primary.test
  Normal   Synced  27m (x2 over 28m)  flagger  (combined from similar events): Promotion completed! Scaling down podinfo.test

ロールバック

次にロールバックの場合を見てみます。先ほどと同様コンテナイメージを更新後、 flagger-loadtester から特定のエンドポイント (http://podinfo-canary:9898/status/500) を叩き、エラーを発生させます。その結果、Canary Deploymentは失敗・中断され、Canary Podが削除されます。

# watch
$ kubectl get canary -n test -w
$ kubectl get pods -n test -w


# コンテナイメージの更新
$ kubectl -n test set image deployment/podinfo podinfod=stefanprodan/podinfo:3.1.2

flagger-loadtester からcurlを実行します。

$ kubectl get pods -n test
NAME                                  READY   STATUS    RESTARTS   AGE
flagger-loadtester-64695f854f-6mkgj   2/2     Running   0          85m
podinfo-75459749c9-8w9tl              2/2     Running   0          3m47s
podinfo-75459749c9-mqd7m              2/2     Running   0          3m51s
podinfo-primary-76655b74b9-b99lq      2/2     Running   0          39m
podinfo-primary-76655b74b9-cvs9z      2/2     Running   0          39m


# エラーを発生させる
$ kubectl exec -it flagger-loadtester-64695f854f-6mkgj -n test sh
/home/app $ watch curl http://podinfo-canary:9898/status/500

Deployment Failed

# Canary
NAME      STATUS      WEIGHT   LASTTRANSITIONTIME
podinfo   Succeeded   0        2021-07-21T10:21:46Z

podinfo   Progressing   0        2021-07-21T10:55:46Z

## Analysisが成功しないためにWEIGHTの値が変化しない
podinfo   Progressing   10       2021-07-21T10:56:46Z

podinfo   Progressing   10       2021-07-21T10:57:46Z
podinfo   Progressing   10       2021-07-21T10:58:46Z
podinfo   Progressing   10       2021-07-21T10:59:46Z
podinfo   Progressing   10       2021-07-21T11:00:46Z

podinfo   Progressing   10       2021-07-21T11:01:46Z

## 最終的にはFailedとなる
podinfo   Failed        0        2021-07-21T11:02:46Z


# Pod
NAME                                  READY   STATUS    RESTARTS   AGE
flagger-loadtester-64695f854f-6mkgj   2/2     Running   0          81m
podinfo-primary-76655b74b9-b99lq      2/2     Running   0          35m
podinfo-primary-76655b74b9-cvs9z      2/2     Running   0          35m

## Canary Podの作成
podinfo-75459749c9-mqd7m              0/2     Pending   0          0s
podinfo-75459749c9-mqd7m              0/2     Pending   0          0s
podinfo-75459749c9-mqd7m              0/2     Init:0/1   0          0s
podinfo-75459749c9-mqd7m              0/2     PodInitializing   0          2s
podinfo-75459749c9-8w9tl              0/2     Pending           0          0s
podinfo-75459749c9-8w9tl              0/2     Pending           0          0s
podinfo-75459749c9-8w9tl              0/2     Init:0/1          0          0s
podinfo-75459749c9-8w9tl              0/2     PodInitializing   0          2s
podinfo-75459749c9-8w9tl              0/2     Running           0          8s
podinfo-75459749c9-8w9tl              1/2     Running           0          10s
podinfo-75459749c9-mqd7m              0/2     Running           0          15s
podinfo-75459749c9-mqd7m              1/2     Running           0          16s
podinfo-75459749c9-8w9tl              2/2     Running           0          19s
podinfo-75459749c9-mqd7m              2/2     Running           0          24s



## Canary Podの削除
podinfo-75459749c9-mqd7m              2/2     Terminating       0          7m
podinfo-75459749c9-8w9tl              2/2     Terminating       0          6m56s
podinfo-75459749c9-mqd7m              0/2     Terminating       0          7m6s
podinfo-75459749c9-8w9tl              0/2     Terminating       0          7m2s
podinfo-75459749c9-mqd7m              0/2     Terminating       0          7m7s
podinfo-75459749c9-mqd7m              0/2     Terminating       0          7m7s
podinfo-75459749c9-8w9tl              0/2     Terminating       0          7m11s
podinfo-75459749c9-8w9tl              0/2     Terminating       0          7m11s

最終的には以下のような状態となります。

$ kubectl get canary -n test
NAME      STATUS   WEIGHT   LASTTRANSITIONTIME
podinfo   Failed   0        2021-07-21T11:02:46Z

$ kubectl get pods -n test
NAME                                  READY   STATUS    RESTARTS   AGE
flagger-loadtester-64695f854f-6mkgj   2/2     Running   0          91m
podinfo-primary-76655b74b9-b99lq      2/2     Running   0          45m
podinfo-primary-76655b74b9-cvs9z      2/2     Running   0          45m

podinfo Canaryのログは以下の通りです。

$ kubectl describe canary -n test podinfo

(中略)

Events:
  Type     Reason  Age                  From     Message
  ----     ------  ----                 ----     -------
  Normal   Synced  53m                  flagger  Advance podinfo.test canary weight 20
  Normal   Synced  52m                  flagger  Advance podinfo.test canary weight 30
  Normal   Synced  51m                  flagger  Advance podinfo.test canary weight 40
  Normal   Synced  50m                  flagger  Advance podinfo.test canary weight 50
  Normal   Synced  49m                  flagger  Copying podinfo.test template spec to podinfo-primary.test
  Normal   Synced  47m (x2 over 48m)    flagger  (combined from similar events): Promotion completed! Scaling down podinfo.test
  Normal   Synced  13m (x2 over 55m)    flagger  New revision detected! Scaling up podinfo.test
  Normal   Synced  12m (x2 over 54m)    flagger  Advance podinfo.test canary weight 10
  Normal   Synced  12m (x2 over 54m)    flagger  Pre-rollout check acceptance-test passed
  Normal   Synced  12m (x2 over 54m)    flagger  Starting canary analysis for podinfo.test
  Warning  Synced  10m                  flagger  Halt podinfo.test advancement success rate 97.50% < 99%
  Warning  Synced  8m25s                flagger  Halt podinfo.test advancement success rate 97.61% < 99%
  Warning  Synced  7m25s (x3 over 11m)  flagger  Halt podinfo.test advancement success rate 0.00% < 99%
  Warning  Synced  6m25s                flagger  Rolling back podinfo.test failed checks threshold reached 5
  Warning  Synced  6m25s                flagger  Canary failed! Scaling down podinfo.test

Blue/Green Deployment

続いてBlue/Green Deploymentを試します。Canary Deploymentから連続して実施する場合は、まず podinfo Canaryリソースを一度削除します。

$ kubectl delete -f podinfo-canary.yaml
canary.flagger.app "podinfo" deleted

podinfo Canaryを削除すると、作成時に一緒に作られたPodなどのリソースも合わせて削除されます。また、Canary作成時に置き換えられた podinfo Podは、Canaryを削除しても復旧されません。

# podinfo Podは再作成されない
$ kubectl get pods -n test
NAME                                  READY   STATUS    RESTARTS   AGE
flagger-loadtester-64695f854f-6mkgj   2/2     Running   0          126m

$ kubectl get deploy -n test
NAME                 READY   UP-TO-DATE   AVAILABLE   AGE
flagger-loadtester   1/1     1            1           126m
podinfo              0/0     0            0           130m

ここでは podinfo Deploymentを編集して podinfo Podを再作成します。編集したのはレプリカ数とコンテナイメージタグの2か所です。

$ kubectl edit deploy podinfo -n test
deployment.apps/podinfo edited


# Deployment編集後
$ kubectl get pods -n test
NAME                                  READY   STATUS    RESTARTS   AGE
flagger-loadtester-64695f854f-h9jdv   2/2     Running   0          33m
podinfo-99dc84b6f-svnvg               2/2     Running   0          25s
podinfo-99dc84b6f-vmv7n               2/2     Running   0          25s

Canary リソースのデプロイ

次に新しいCanaryリソースをデプロイします。Canary Deploymentの際に利用したものと比べ、Analysis部分に maxWeight stepWeight がありません。Weightの設定がないため、Canary Podへトラフィックを流すことなく、spec.analysis.iterations の回数だけAnalysisを実行、チェックを通過したら新しいバージョンのPrimary Podを作成してトラフィックを切り替えます。


podinfo-bg.yaml

apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
  name: podinfo
  namespace: test
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: podinfo
  progressDeadlineSeconds: 60
  autoscalerRef:
    apiVersion: autoscaling/v2beta2
    kind: HorizontalPodAutoscaler
    name: podinfo
  service:
    port: 9898
    targetPort: 9898
    gateways:
    - public-gateway.istio-system.svc.cluster.local
    hosts:
    - app.example.com
    trafficPolicy:
      tls:
        mode: DISABLE
    retries:
      attempts: 3
      perTryTimeout: 1s
      retryOn: "gateway-error,connect-failure,refused-stream"
  analysis:
    interval: 1m
    threshold: 5
    iterations: 10
    metrics:
    - name: request-success-rate
      thresholdRange:
        min: 99
      interval: 1m
    - name: request-duration
      thresholdRange:
        max: 500
      interval: 30s
    webhooks:
      - name: acceptance-test
        type: pre-rollout
        url: http://flagger-loadtester.test/
        timeout: 30s
        metadata:
          type: bash
          cmd: "curl -sd 'test' http://podinfo-canary:9898/token | grep token"
      - name: load-test
        url: http://flagger-loadtester.test/
        timeout: 5s
        metadata:
          cmd: "hey -z 1m -q 10 -c 2 http://podinfo-canary.test:9898/"


まずは上記マニフェストファイルをデプロイします。

$ kubectl apply -f podinfo-bg.yaml
canary.flagger.app/podinfo created


# デプロイ後の確認
$ kubectl get pods -n test
NAME                                  READY   STATUS    RESTARTS   AGE
flagger-loadtester-64695f854f-h9jdv   2/2     Running   0          38m
podinfo-primary-7b67c7fbc4-4fhm6      2/2     Running   0          82s
podinfo-primary-7b67c7fbc4-w5jpt      2/2     Running   0          82s

$ kubectl get canary -n test
NAME      STATUS        WEIGHT   LASTTRANSITIONTIME
podinfo   Initialized   0        2021-07-22T00:46:56Z

アプリケーションのアップデート

次にアプリケーションを更新します。

Canary Deploymentと同様、イメージタグの更新を行います。

$ kubectl -n test set image deployment/podinfo podinfod=stefanprodan/podinfo:3.1.1
deployment.apps/podinfo image updated

PodとCanaryの経過を見てみます。Canary Deploymentと異なり、こちらのログからは経過が確認できませんが、 kubectl describe canary でのEventsにはAnalysisが進行している様子が確認できます。

Progressing

# Progressing
$ kubectl get canary -n test -w
NAME      STATUS        WEIGHT   LASTTRANSITIONTIME
podinfo   Initialized   0        2021-07-22T00:46:56Z

podinfo   Progressing   0        2021-07-22T00:47:56Z


$ kubectl get pods -n test  -w
NAME                                  READY   STATUS    RESTARTS   AGE
flagger-loadtester-64695f854f-h9jdv   2/2     Running   0          38m
podinfo-primary-7b67c7fbc4-4fhm6      2/2     Running   0          93s
podinfo-primary-7b67c7fbc4-w5jpt      2/2     Running   0          93s


## Canary Podの作成
podinfo-5f8fd4f546-5tbmv              0/2     Pending   0          0s
podinfo-5f8fd4f546-5tbmv              0/2     Pending   0          0s
podinfo-5f8fd4f546-5tbmv              0/2     Init:0/1   0          0s
podinfo-5f8fd4f546-5tbmv              0/2     PodInitializing   0          2s
podinfo-5f8fd4f546-5tbmv              0/2     Running           0          3s
podinfo-5f8fd4f546-5tbmv              1/2     Running           0          3s
podinfo-5f8fd4f546-bkxbz              0/2     Pending           0          0s
podinfo-5f8fd4f546-bkxbz              0/2     Pending           0          1s
podinfo-5f8fd4f546-bkxbz              0/2     Init:0/1          0          1s
podinfo-5f8fd4f546-bkxbz              0/2     PodInitializing   0          3s
podinfo-5f8fd4f546-bkxbz              0/2     Running           0          4s
podinfo-5f8fd4f546-bkxbz              1/2     Running           0          5s
podinfo-5f8fd4f546-bkxbz              2/2     Running           0          9s
podinfo-5f8fd4f546-5tbmv              2/2     Running           0          16s


## Iterationが進行している
$ kubectl describe canary podinfo -n test | tail -10
Events:
  Type     Reason  Age                    From     Message
  ----     ------  ----                   ----     -------
  Warning  Synced  3m24s                  flagger  podinfo-primary.test not ready: waiting for rollout to finish: observed deployment generation less then desired generation
  Normal   Synced  2m24s (x2 over 3m24s)  flagger  all the metrics providers are available!
  Normal   Synced  2m24s                  flagger  Initialization done! podinfo.test
  Normal   Synced  84s                    flagger  New revision detected! Scaling up podinfo.test
  Normal   Synced  24s                    flagger  Starting canary analysis for podinfo.test
  Normal   Synced  24s                    flagger  Pre-rollout check acceptance-test passed
  Normal   Synced  24s                    flagger  Advance podinfo.test canary iteration 1/10

$ kubectl describe canary podinfo -n test | tail -10
  Normal   Synced  11m (x2 over 12m)    flagger  all the metrics providers are available!
  Normal   Synced  11m                  flagger  Initialization done! podinfo.test
  Normal   Synced  10m                  flagger  New revision detected! Scaling up podinfo.test
  Normal   Synced  9m26s                flagger  Starting canary analysis for podinfo.test
  Normal   Synced  9m26s                flagger  Pre-rollout check acceptance-test passed
  Normal   Synced  9m26s                flagger  Advance podinfo.test canary iteration 1/10
  Normal   Synced  8m26s                flagger  Advance podinfo.test canary iteration 2/10
  Normal   Synced  7m26s                flagger  Advance podinfo.test canary iteration 3/10
  Normal   Synced  6m26s                flagger  Advance podinfo.test canary iteration 4/10
  Normal   Synced  26s (x6 over 5m26s)  flagger  (combined from similar events): Advance podinfo.test canary iteration 10/10

Promoting

# Promoting
NAME      STATUS        WEIGHT   LASTTRANSITIONTIME
podinfo   Promoting     0        2021-07-22T00:59:56Z


## Primary Podの更新
NAME                                  READY   STATUS    RESTARTS   AGE
podinfo-primary-7b67c7fbc4-w5jpt      2/2     Terminating       0          14m
podinfo-primary-666bd89c86-s8pkg      0/2     Pending           0          0s
podinfo-primary-666bd89c86-s8pkg      0/2     Pending           0          0s
podinfo-primary-666bd89c86-s8pkg      0/2     Init:0/1          0          0s
podinfo-primary-666bd89c86-vqhm5      0/2     Pending           0          0s
podinfo-primary-666bd89c86-vqhm5      0/2     Pending           0          0s
podinfo-primary-666bd89c86-vqhm5      0/2     Init:0/1          0          0s
podinfo-primary-666bd89c86-s8pkg      0/2     PodInitializing   0          2s
podinfo-primary-666bd89c86-vqhm5      0/2     PodInitializing   0          2s
podinfo-primary-666bd89c86-s8pkg      0/2     Running           0          3s
podinfo-primary-666bd89c86-vqhm5      0/2     Running           0          3s
podinfo-primary-666bd89c86-s8pkg      1/2     Running           0          4s
podinfo-primary-666bd89c86-vqhm5      1/2     Running           0          4s
podinfo-primary-7b67c7fbc4-w5jpt      0/2     Terminating       0          14m
podinfo-primary-7b67c7fbc4-w5jpt      0/2     Terminating       0          14m
podinfo-primary-7b67c7fbc4-w5jpt      0/2     Terminating       0          14m
podinfo-primary-666bd89c86-vqhm5      2/2     Running           0          16s
podinfo-primary-666bd89c86-s8pkg      2/2     Running           0          16s
podinfo-primary-7b67c7fbc4-4fhm6      2/2     Terminating       0          14m
podinfo-primary-7b67c7fbc4-4fhm6      0/2     Terminating       0          14m
podinfo-primary-7b67c7fbc4-4fhm6      0/2     Terminating       0          14m
podinfo-primary-7b67c7fbc4-4fhm6      0/2     Terminating       0          14m



$ kubectl describe canary podinfo -n test | tail -10
  Normal   Synced  13m (x2 over 14m)  flagger  all the metrics providers are available!
  Normal   Synced  13m                flagger  Initialization done! podinfo.test
  Normal   Synced  12m                flagger  New revision detected! Scaling up podinfo.test
  Normal   Synced  11m                flagger  Starting canary analysis for podinfo.test
  Normal   Synced  11m                flagger  Pre-rollout check acceptance-test passed
  Normal   Synced  11m                flagger  Advance podinfo.test canary iteration 1/10
  Normal   Synced  10m                flagger  Advance podinfo.test canary iteration 2/10
  Normal   Synced  9m5s               flagger  Advance podinfo.test canary iteration 3/10
  Normal   Synced  8m5s               flagger  Advance podinfo.test canary iteration 4/10
  Normal   Synced  5s (x8 over 7m5s)  flagger  (combined from similar events): Copying podinfo.test template spec to podinfo-primary.test

Finalising

# Finalising
NAME      STATUS        WEIGHT   LASTTRANSITIONTIME
podinfo   Finalising    0        2021-07-22T01:00:56Z


## Primary Podへトラフィックを流す
$ kubectl describe canary podinfo -n test | tail -10
  Normal   Synced  14m (x2 over 15m)    flagger  all the metrics providers are available!
  Normal   Synced  14m                  flagger  Initialization done! podinfo.test
  Normal   Synced  13m                  flagger  New revision detected! Scaling up podinfo.test
  Normal   Synced  12m                  flagger  Starting canary analysis for podinfo.test
  Normal   Synced  12m                  flagger  Pre-rollout check acceptance-test passed
  Normal   Synced  12m                  flagger  Advance podinfo.test canary iteration 1/10
  Normal   Synced  11m                  flagger  Advance podinfo.test canary iteration 2/10
  Normal   Synced  10m                  flagger  Advance podinfo.test canary iteration 3/10
  Normal   Synced  9m15s                flagger  Advance podinfo.test canary iteration 4/10
  Normal   Synced  15s (x9 over 8m15s)  flagger  (combined from similar events): Routing all traffic to primary

Succeeded

# Succeeded
NAME      STATUS        WEIGHT   LASTTRANSITIONTIME
podinfo   Succeeded     0        2021-07-22T01:01:56Z


## Canary Podの削除
NAME                                  READY   STATUS    RESTARTS   AGE
podinfo-5f8fd4f546-5tbmv              2/2     Terminating       0          14m
podinfo-5f8fd4f546-bkxbz              2/2     Terminating       0          13m
podinfo-5f8fd4f546-5tbmv              0/2     Terminating       0          14m
podinfo-5f8fd4f546-bkxbz              0/2     Terminating       0          14m
podinfo-5f8fd4f546-5tbmv              0/2     Terminating       0          14m
podinfo-5f8fd4f546-5tbmv              0/2     Terminating       0          14m
podinfo-5f8fd4f546-bkxbz              0/2     Terminating       0          14m
podinfo-5f8fd4f546-bkxbz              0/2     Terminating       0          14m


$ kubectl describe canary podinfo -n test | tail -10
  Normal   Synced  15m (x2 over 16m)  flagger  all the metrics providers are available!
  Normal   Synced  15m                flagger  Initialization done! podinfo.test
  Normal   Synced  14m                flagger  New revision detected! Scaling up podinfo.test
  Normal   Synced  13m                flagger  Starting canary analysis for podinfo.test
  Normal   Synced  13m                flagger  Pre-rollout check acceptance-test passed
  Normal   Synced  13m                flagger  Advance podinfo.test canary iteration 1/10
  Normal   Synced  12m                flagger  Advance podinfo.test canary iteration 2/10
  Normal   Synced  11m                flagger  Advance podinfo.test canary iteration 3/10
  Normal   Synced  10m                flagger  Advance podinfo.test canary iteration 4/10
  Normal   Synced  0s (x10 over 9m)   flagger  (combined from similar events): Promotion completed! Scaling down podinfo.test

ロールバック

ロールバックも試してみます。やることはCanary Deploymentとまったく同じです。

# コンテナイメージの更新
$ kubectl -n test set image deployment/podinfo podinfod=stefanprodan/podinfo:3.1.2
deployment.apps/podinfo image updated


# Canary/Podのウォッチを開始
$ kubectl get canary -n test -w
NAME      STATUS      WEIGHT   LASTTRANSITIONTIME
podinfo   Succeeded   0        2021-07-22T01:01:56Z


$ kubectl get pods -n test  -w
NAME                                  READY   STATUS    RESTARTS   AGE
flagger-loadtester-64695f854f-h9jdv   2/2     Running   0          66m
podinfo-primary-666bd89c86-s8pkg      2/2     Running   0          15m
podinfo-primary-666bd89c86-vqhm5      2/2     Running   0          15m
# flagger-loadtesterから500ステータスコードの実行
$ kubectl get pods -n test
NAME                                  READY   STATUS    RESTARTS   AGE
flagger-loadtester-64695f854f-6mkgj   2/2     Running   0          85m
podinfo-75459749c9-8w9tl              2/2     Running   0          3m47s
podinfo-75459749c9-mqd7m              2/2     Running   0          3m51s
podinfo-primary-76655b74b9-b99lq      2/2     Running   0          39m
podinfo-primary-76655b74b9-cvs9z      2/2     Running   0          39m

$ kubectl exec -it -n test flagger-loadtester-64695f854f-6mkgj sh
/home/app $ watch curl http://podinfo-canary.test:9898/status/500

Deployment Proceeding (Failed)

$ kubectl describe canary podinfo -n test | tail -10
  Normal   Synced  31m                 flagger  Advance podinfo.test canary iteration 2/10
  Normal   Synced  30m                 flagger  Advance podinfo.test canary iteration 3/10
  Normal   Synced  29m                 flagger  Advance podinfo.test canary iteration 4/10
  Normal   Synced  19m (x10 over 28m)  flagger  (combined from similar events): Promotion completed! Scaling down podinfo.test
  Normal   Synced  5m4s (x2 over 33m)  flagger  New revision detected! Scaling up podinfo.test
  Normal   Synced  4m4s (x2 over 32m)  flagger  Advance podinfo.test canary iteration 1/10
  Normal   Synced  4m4s (x2 over 32m)  flagger  Pre-rollout check acceptance-test passed
  Normal   Synced  4m4s (x2 over 32m)  flagger  Starting canary analysis for podinfo.test
  Warning  Synced  64s (x2 over 3m4s)  flagger  Halt podinfo.test advancement success rate 0.00% < 99%
  Warning  Synced  4s (x2 over 2m4s)   flagger  Halt podinfo.test advancement success rate 97.51% < 99%

Deployment Failed

NAME      STATUS      WEIGHT   LASTTRANSITIONTIME
podinfo   Failed        0        2021-07-22T01:22:56Z


NAME                                  READY   STATUS    RESTARTS   AGE
podinfo-75459749c9-w8vx8              2/2     Terminating       0          7m
podinfo-75459749c9-7qbvd              2/2     Terminating       0          7m
podinfo-75459749c9-w8vx8              0/2     Terminating       0          7m6s
podinfo-75459749c9-7qbvd              0/2     Terminating       0          7m6s
podinfo-75459749c9-7qbvd              0/2     Terminating       0          7m13s
podinfo-75459749c9-7qbvd              0/2     Terminating       0          7m13s
podinfo-75459749c9-w8vx8              0/2     Terminating       0          7m15s
podinfo-75459749c9-w8vx8              0/2     Terminating       0          7m15s


$ kubectl describe canary podinfo -n test | tail -10
  Normal   Synced  32m                    flagger  Advance podinfo.test canary iteration 4/10
  Normal   Synced  22m (x10 over 31m)     flagger  (combined from similar events): Promotion completed! Scaling down podinfo.test
  Normal   Synced  8m32s (x2 over 36m)    flagger  New revision detected! Scaling up podinfo.test
  Normal   Synced  7m32s (x2 over 35m)    flagger  Pre-rollout check acceptance-test passed
  Normal   Synced  7m32s (x2 over 35m)    flagger  Advance podinfo.test canary iteration 1/10
  Normal   Synced  7m32s (x2 over 35m)    flagger  Starting canary analysis for podinfo.test
  Warning  Synced  3m32s (x2 over 5m32s)  flagger  Halt podinfo.test advancement success rate 97.51% < 99%
  Warning  Synced  2m32s (x3 over 6m32s)  flagger  Halt podinfo.test advancement success rate 0.00% < 99%
  Warning  Synced  92s                    flagger  Rolling back podinfo.test failed checks threshold reached 5
  Warning  Synced  92s                    flagger  Canary failed! Scaling down podinfo.test

Slackへのメッセージ送信

次にSlackへメッセージを送信する場合を試してみます。Flaggerでは AlertProvider というリソースで、メッセージの送信先となるプロバイダーやWebhooknURLを設定します。

※参考リンク:Flagger Doc - Alerting

今回は以下のような AlertProvider マニフェストを利用します。前提として、SlackのIncoming Webhookを取得しておきます。

※参考リンク:SlackでのIncoming Webhookの利用


podinfo-alert.yaml

apiVersion: flagger.app/v1beta1
kind: AlertProvider
metadata:
  name: on-call
  namespace: test
spec:
  type: slack
  channel: on-call-alerts
  username: flagger
  secretRef:
    name: on-call-url
---
apiVersion: v1
kind: Secret
metadata:
  name: on-call-url
  namespace: test
data:
  address: <encoded webhook URL>


上記マニフェストを使ってリソースをデプロイします。

$ kubectl apply -f podinfo-alert.yaml
alertprovider.flagger.app/on-call created
secret/on-call-url created


# デプロイ後の確認
$ kubectl get alertprovider -n test
NAME      TYPE
on-call   slack

AlertProvider を利用するには Canary リソースのほうも修正が必要です。以下のマニフェストのように、 spec.analysis.alerts にて、利用する AlertProvider とログレベルを指定します。


podinfo-canary-alert.yaml

apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
  name: podinfo
  namespace: test
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: podinfo
  progressDeadlineSeconds: 60
  autoscalerRef:
    apiVersion: autoscaling/v2beta2
    kind: HorizontalPodAutoscaler
    name: podinfo
  service:
    port: 9898
    targetPort: 9898
    gateways:
    - public-gateway.istio-system.svc.cluster.local
    hosts:
    - app.example.com
    trafficPolicy:
      tls:
        mode: DISABLE
    retries:
      attempts: 3
      perTryTimeout: 1s
      retryOn: "gateway-error,connect-failure,refused-stream"
  analysis:
    alerts:
      - name: "on-call Slack"
        severity: info
        providerRef:
          name: on-call
          namespace: test
    interval: 1m
    threshold: 5
    maxWeight: 50
    stepWeight: 10
    metrics:
    - name: request-success-rate
      thresholdRange:
        min: 99
      interval: 1m
    - name: request-duration
      thresholdRange:
        max: 500
      interval: 30s
    webhooks:
      - name: acceptance-test
        type: pre-rollout
        url: http://flagger-loadtester.test/
        timeout: 30s
        metadata:
          type: bash
          cmd: "curl -sd 'test' http://podinfo-canary:9898/token | grep token"
      - name: load-test
        url: http://flagger-loadtester.test/
        timeout: 5s
        metadata:
          cmd: "hey -z 1m -q 10 -c 2 http://podinfo-canary.test:9898/"


上記マニフェストから Canary リソースをデプロイします。

$ kubectl apply -f podinfo-canary-alert.yaml
canary.flagger.app/podinfo created

$ kubectl get pods -n test
NAME                                  READY   STATUS    RESTARTS   AGE
flagger-loadtester-64695f854f-h9jdv   2/2     Running   0          104m
podinfo-primary-df5797b7c-bj86k       2/2     Running   0          90s
podinfo-primary-df5797b7c-xgf4v       2/2     Running   0          90s

$ kubectl get canary -n test
NAME      STATUS        WEIGHT   LASTTRANSITIONTIME
podinfo   Initialized   0        2021-07-22T01:53:06Z

Slackとの連携が成功していれば、以下の画像のようなメッセージが送られます。

Canary リソース作成時

f:id:FY0323:20210722181703j:plain

※ コンテナイメージアップデート成功時

f:id:FY0323:20210722181743j:plain

※ アップデート失敗時

f:id:FY0323:20210722181807j:plain

参考ドキュメント

Flux v2に入門する

今回はGitOpsツールの一つであるFlux v2を試してみます。GitOpsについては以下の参考リンクなどをご参照ください。

※参考リンク:

Flux v2とは

Flux v2は、複数のCustom Controllerを組み合わせて、GitOpsの各機能を提供します。

Fluxの構成は以下の通りです。

Flux-component

※画像:Fluxドキュメントより

Fluxが実現する機能として、以下のような機能が挙げられています。

GitなどのリソースとKubernetesクラスター間の"同期"

FluxはGitHub/GitLabなどのGitリポジトリ、Helmリポジトリ、そしてminioのようなS3-compatibleなオブジェクトバケットなどを、リソースとして管理し、Kubernetesクラスターとの同期を実行します。

source-controllers ※画像:Fluxドキュメントより

コンテナイメージの自動アップデート

Fluxはコンテナレジストリもリソースとして管理し、コンテナイメージのアップデートを検知します。またイメージ更新を検知すると、対象のKubernetesマニフェストファイルのイメージタグを自動的に更新し、最終的にはクラスター上のPodのイメージをアップデートします。

image-update-controllers ※画像:Fluxドキュメントより

リソースの状態変化の通知

Fluxは、管理するリソースの状態変化に応じて、SlackやDiscordなどの外部システムに通知を送る機能が備わっています。

notification-controllers ※画像:Fluxドキュメントより

その他

  • リソース間依存関係の管理: Fluxの Kustomization HelmRelease リソースには spec.dependsOn というフィールドがあり、依存関係のリソースを指定することで、対象のリソースが作成・起動するまでは、リソースの作成を行わないよう制御できます。

※参考リンク:Flux v2のdependsOnを簡単に検証してみた

  • 外部イベントの監視と応答:Fluxの Receiver というリソースではWebhook receiverを定義し、Reconciliationのトリガーとして利用できます。利用できるリソースには、GitHubやDockerHubなどがあります。

  • 他ソフトウェアとの相互運用性GitHub Actions / Argo / Tektonなどのワークフロープロバイダ、クラスター管理を行うCluster APIなどのソフトウェアとの相互運用性を提供します。

Flux v1 / v2の違い

Flux v2は、以前はv1として開発が進んでいましたが、長年要望されていた機能をより簡単に実装できるよう、v2として大きな変更を加えました。v1ではモノリスなオペレーターで全ての機能を実現していたのに対し、v2では各機能を専用のCustom Controllerに実装することで分離しています。

v1とv2の機能差分はこちらのページに記載されていますが、特に大きな違いは以下のあたりかと思います。

  • v1

    • 一つのGitリポジトリの同期をサポート
    • flux deploy時のみ、Gitのコンフィグや秘匿情報を指定可能
    • HEADの追跡のみサポート
  • v2

    • 複数のGitリポジトリの同期をサポート
    • GitRepositoryリソースでGitリポジトリの設定などを定義
    • 指定したブランチやタグの追跡をサポート
    • Prometheusなど、エコシステムのコアのコンポーネントと統合できる
    • マルチテナンシーをサポート(参考リンク

なお、現在v1はメンテナンスモードで、新機能の追加は行われず、バグ修正とCVEのパッチのみ対応しております。

Fluxではv2の利用が推奨されています。まだGAには至っていませんが、こちらのロードマップに沿って開発が進められています。

Flux v2を試す

ここから実際にFluxを動かしてみます。

前提条件の確認と動作環境

ここからはGet Startedの手順をベースに動かしてみます。操作を行う上での前提条件は以下の通りです。

  • k8s version: 1.16 以上
  • kubectl: 1.18 以上
  • GitHubリポジトリの用意
  • GitHub Personal Access Tokenの用意:scopeでは repo 配下の権限をすべて許可しておきます。

また今回は Amazon EKS上で動作検証を行いました。動作確認を行った環境の情報は以下の通りです。

$ eksctl version
0.57.0

$ eksctl create cluster -f eks-clusterconfig.yml

$ kubectl version
Client Version: version.Info{Major:"1", Minor:"20", GitVersion:"v1.20.0", GitCommit:"af46c47ce925f4c4ad5cc8d1fca46c7b77d13b38", GitTreeState:"clean", BuildDate:"2020-12-08T17:59:43Z", GoVersion:"go1.15.5", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"20+", GitVersion:"v1.20.4-eks-6b7464", GitCommit:"6b746440c04cb81db4426842b4ae65c3f7035e53", GitTreeState:"clean", BuildDate:"2021-03-19T19:33:03Z", GoVersion:"go1.15.8", Compiler:"gc", Platform:"linux/amd64"}

$ kubectl get nodes
NAME                                               STATUS   ROLES    AGE   VERSION
ip-192-168-0-173.ap-northeast-1.compute.internal   Ready    <none>   25m   v1.20.4-eks-6b7464
ip-192-168-1-253.ap-northeast-1.compute.internal   Ready    <none>   25m   v1.20.4-eks-6b7464
ip-192-168-2-59.ap-northeast-1.compute.internal    Ready    <none>   25m   v1.20.4-eks-6b7464

また、後ほどFluxからGitHubへのアクセスに利用する環境変数を設定しておきます。

export GITHUB_TOKEN=<GitHub Personal Access Token>
export GITHUB_USER=fy0323

Flux CLIのインストール

GitHubリポジトリとEKSクラスターが用意できたので、次に flux CLIのインストールを行います。Fluxは flux CLIを利用して、Flux自体のデプロイやリソースの作成・管理を行うことができます。今回の手順でも、リソースの作成や制御はほぼ flux CLIを利用して行います。

$ curl -s https://fluxcd.io/install.sh | sudo bash
[INFO]  Downloading metadata https://api.github.com/repos/fluxcd/flux2/releases/latest
[INFO]  Using 0.16.1 as release
[INFO]  Downloading hash https://github.com/fluxcd/flux2/releases/download/v0.16.1/flux_0.16.1_checksums.txt
[INFO]  Downloading binary https://github.com/fluxcd/flux2/releases/download/v0.16.1/flux_0.16.1_linux_amd64.tar.gz
[INFO]  Verifying binary download
[INFO]  Installing flux to /usr/local/bin/flux

$ flux --version
flux version 0.16.1

Fluxデプロイ

Fluxのデプロイを行うコマンドには flux install flux bootstrap の2種類があります。 flux install はFluxをクラスター上にインストールするだけですが、 flux bootstrap は、サブコマンドに指定したGitプロバイダに対して、Fluxインストール用のマニフェストファイルをコミットします。その後、コミットしたリポジトリをFluxの同期対象とし、クラスターと同期をすることでFluxのデプロイを実現します。

ここでは flux bootstrap コマンドを利用します。まずは flux check コマンドを実行し、Fluxのデプロイに必要な前提条件を満たすことを確認します。

$ flux check --pre
► checking prerequisites
✔ kubectl 1.20.0 >=1.18.0-0
✔ Kubernetes 1.20.4-eks-6b7464 >=1.16.0-0
✔ prerequisites checks passed

チェックで問題がないため、Fluxのデプロイを実行します。

$ flux bootstrap github \
>   --owner=$GITHUB_USER \
>   --repository=fluxv2-test \
>   --branch=main \
>   --path=./clusters/my-cluster \
>   --personal

(中略)

✔ source-controller: deployment ready
✔ kustomize-controller: deployment ready
✔ helm-controller: deployment ready
✔ notification-controller: deployment ready
✔ all components are healthy

flux bootstrap コマンド後、クラスター上にリソースが作成されたことが確認できます。

$ kubectl get pods -n flux-system
NAME                                       READY   STATUS    RESTARTS   AGE
helm-controller-5b96d94c7f-525lt           1/1     Running   0          13m
kustomize-controller-5b95b78ddc-wqkmg      1/1     Running   0          13m
notification-controller-55f94bc746-zx8m9   1/1     Running   0          13m
source-controller-78bfb8576-ftnxl          1/1     Running   0          13m

$ kubectl get gitrepository -n flux-system
NAME          URL                                       READY   STATUS                                                            AGE
flux-system   ssh://git@github.com/fy0323/fluxv2-test   True    Fetched revision: main/e1d0fb9ed858d425e0bce380ab096a34c4e7035c   15m

GitHubリポジトリとの同期によるアプリケーションのデプロイ

続いてアプリケーションのマニフェストファイルを含むGitHubリポジトリを登録し、クラスター間の同期によってデプロイを行います。

※参考リンク:

まずは作業用のリポジトリをクローンします。次に GitRepository というリソースを用意しますが、ここでは flux create コマンドを利用してマニフェストファイルを生成、リポジトリに保存することでクラスターと同期し、自動的にデプロイされる、という流れで作業します。

$ git clone https://github.com/fy0323/fluxv2-test
$ cd fluxv2-test/

# GitRepositoryリソースのマニフェストファイル生成
$ flux create source git podinfo \
>   --url=https://github.com/stefanprodan/podinfo \
>   --branch=master \
>   --interval=30s \
>   --export > ./clusters/my-cluster/podinfo-source.yaml

# コミット
$ git add .
$ git commit -m "add podinfo gitrepository"
$ git push

なお、上記コマンドで生成されたマニフェストファイルは以下のようになります。

podinfo-source.yaml

apiVersion: source.toolkit.fluxcd.io/v1beta1
kind: GitRepository
metadata:
  name: podinfo
  namespace: flux-system
spec:
  interval: 30s
  ref:
    branch: master
  url: https://github.com/stefanprodan/podinfo

次に Kustomization というリソースを作成します。このリソースはその名の通り kustomizeが利用する kustomization.yaml のパスを指定し、Kubernetesマニフェストファイルの管理元を定義するリソースとなります。

# Kustomaizationリソースのマニフェストファイル生成
$ flux create kustomization podinfo \
>   --source=podinfo \
>   --path="./kustomize" \
>   --prune=true \
>   --validation=client \
>   --interval=5m \
>   --export > ./clusters/my-cluster/podinfo-kustomization.yaml

# コミット
$ git add .
$ git commit -m "add podinfo kustomization"
$ git push

podinfo-kustomization.yaml

---
apiVersion: kustomize.toolkit.fluxcd.io/v1beta1
kind: Kustomization
metadata:
  name: podinfo
  namespace: flux-system
spec:
  interval: 5m0s
  path: ./kustomize
  prune: true
  sourceRef:
    kind: GitRepository
    name: podinfo
  validation: client

しばらくするとリポジトリクラスター間での同期が発生し、以下の通り podinfo というアプリケーションがクラスターにデプロイされます。

# kustomizationリソースの状態確認
$ flux get kustomization
NAME            READY   MESSAGE                                                                 REVISION                                        SUSPENDED
flux-system     True    Applied revision: main/7e17dea66b87e57121472eb95abe876184c9ee8b         main/7e17dea66b87e57121472eb95abe876184c9ee8b   False
podinfo         True    Applied revision: master/627d5c4bb67b77185f37e31d734b085019ff2951       master/627d5c4bb67b77185f37e31d734b085019ff2951 False


# podinfoがデプロイされている
$ kubectl get all
NAME                           READY   STATUS    RESTARTS   AGE
pod/podinfo-576d5bf6bd-qssrz   1/1     Running   0          4m58s
pod/podinfo-576d5bf6bd-rqx8d   1/1     Running   0          5m14s

NAME                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)             AGE
service/kubernetes   ClusterIP   10.100.0.1      <none>        443/TCP             71m
service/podinfo      ClusterIP   10.100.113.99   <none>        9898/TCP,9999/TCP   5m14s

NAME                      READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/podinfo   2/2     2            2           5m14s

NAME                                 DESIRED   CURRENT   READY   AGE
replicaset.apps/podinfo-576d5bf6bd   2         2         2       5m14s

NAME                                          REFERENCE            TARGETS         MINPODS   MAXPODS   REPLICAS   AGE
horizontalpodautoscaler.autoscaling/podinfo   Deployment/podinfo   <unknown>/99%   2         4         2          5m14s

イメージの同期

次に、コンテナイメージの自動同期を試してみます。

※参考リンク:

まずは前項で利用した podinfo の定義ファイルを一度削除します。クラスターからリソースを削除しても、リポジトリと同期することで再作成されるため、リポジトリから削除します。

$ rm clusters/my-cluster/podinfo-kustomization.yaml
$ rm clusters/my-cluster/podinfo-source.yaml
$ git add .
$ git commit -m "remove podinfo-source & podinfo-kustomization"
$ git push

# 削除されたことの確認
$ kubectl get pods
No resources found in default namespace.
$ kubectl get gitrepository -n flux-system
NAME          URL                                       READY   STATUS                                                            AGE
flux-system   ssh://git@github.com/fy0323/fluxv2-test   True    Fetched revision: main/1c5df5beab6e782467663bddab02e33a525ad5a2   51m

次にFluxの再インストールを行います。Fluxのコンテナイメージの管理を行うのは image-reflector image-automation というControllerなのですが、これらはデフォルトではインストールされず、リソースを追加する必要があります。 flux bootstrap によるFluxのインストールは冪等であり、実行時に新しいバージョンがリリースされている場合は、新しいバージョンがインストールされます。

また、コンテナイメージの更新後、上記Controllerにより、マニフェストファイルのイメージタグを書き換えるため、デプロイ用のキーに書き込み権限を付与する必要があります。前項でFluxのインストールを実行した際は、読み取り権限のみを付与したキーが発行されており、これを変更する必要があります。

ここではFluxのキー情報が格納されている flux-system というSecretリソースを削除し、その後Fluxの再インストールを行います。

# Secretの削除
$ kubectl get secret -n flux-system
NAME                                      TYPE                                  DATA   AGE
default-token-649ts                       kubernetes.io/service-account-token   3      90m
flux-system                               Opaque                                3      90m
helm-controller-token-cdrqn               kubernetes.io/service-account-token   3      90m
image-automation-controller-token-8nrpz   kubernetes.io/service-account-token   3      49m
image-reflector-controller-token-p96zm    kubernetes.io/service-account-token   3      49m
kustomize-controller-token-k2zft          kubernetes.io/service-account-token   3      90m
notification-controller-token-z4npl       kubernetes.io/service-account-token   3      90m
source-controller-token-ssh2v             kubernetes.io/service-account-token   3      90m

$ kubectl delete secret -n flux-system flux-system
secret "flux-system" deleted


# Fluxの再インストール
$ flux bootstrap github \
> --components-extra=image-reflector-controller,image-automation-controller \
> --owner=$GITHUB_USER \
> --repository=fluxv2-test \
> --branch=main \
> --path=./clusters/my-cluster \
> --read-write-key \
> --personal

(中略)

✔ image-automation-controller: deployment ready
✔ source-controller: deployment ready
✔ kustomize-controller: deployment ready
✔ helm-controller: deployment ready
✔ notification-controller: deployment ready
✔ image-reflector-controller: deployment ready
✔ all components are healthy


# リソースの確認
$ kubectl get pods -n flux-system
NAME                                           READY   STATUS    RESTARTS   AGE
helm-controller-5b96d94c7f-525lt               1/1     Running   0          41m
image-automation-controller-5cf75fd555-px4r7   1/1     Running   0          45s
image-reflector-controller-6787985855-bssjr    1/1     Running   0          45s
kustomize-controller-5b95b78ddc-wqkmg          1/1     Running   0          41m
notification-controller-55f94bc746-zx8m9       1/1     Running   0          41m
source-controller-78bfb8576-ftnxl              1/1     Running   0          41m

※なお、ここでSecretリソースを削除せずにキー情報が更新されないと、後ほどの操作時に以下のようにエラーが発生します。

コンテナイメージアップデート時のエラー

# コンテナイメージのアップデート時にエラーが発生
$ flux get image update
NAME            READY   MESSAGE                                                                                 LAST RUNSUSPENDED
flux-system     False   remote: ERROR: The key you are authenticating with has been marked as read only.                False

Fluxの再デプロイが完了したので、テスト用のアプリケーションのデプロイを行います。ここではコンテナイメージが 5.0.0 のものを指定します。

# podinfoマニフェストファイルの取得
$ curl -sL https://raw.githubusercontent.com/stefanprodan/podinfo/5.0.0/kustomize/deployment.yaml > ./clusters/my-cluster/podinfo-deployment.yaml

# コミット
$ git add .
$ git commit -m "add podinfo deployment"
$ git push

podinfo-deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: podinfo
spec:
  minReadySeconds: 3
  revisionHistoryLimit: 5
  progressDeadlineSeconds: 60
  strategy:
    rollingUpdate:
      maxUnavailable: 0
    type: RollingUpdate
  selector:
    matchLabels:
      app: podinfo
  template:
    metadata:
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "9797"
      labels:
        app: podinfo
    spec:
      containers:
      - name: podinfod
        image: ghcr.io/stefanprodan/podinfo:5.0.0
        imagePullPolicy: IfNotPresent
        ports:
        - name: http
          containerPort: 9898
          protocol: TCP
        - name: http-metrics
          containerPort: 9797
          protocol: TCP
        - name: grpc
          containerPort: 9999
          protocol: TCP
        command:
        - ./podinfo
        - --port=9898
        - --port-metrics=9797
        - --grpc-port=9999
        - --grpc-service-name=podinfo
        - --level=info
        - --random-delay=false
        - --random-error=false
        env:
        - name: PODINFO_UI_COLOR
          value: "#34577c"
        livenessProbe:
          exec:
            command:
            - podcli
            - check
            - http
            - localhost:9898/healthz
          initialDelaySeconds: 5
          timeoutSeconds: 5
        readinessProbe:
          exec:
            command:
            - podcli
            - check
            - http
            - localhost:9898/readyz
          initialDelaySeconds: 5
          timeoutSeconds: 5
        resources:
          limits:
            cpu: 2000m
            memory: 512Mi
          requests:
            cpu: 100m
            memory: 64Mi

続いてここでは flux reconcile コマンドを利用し、リポジトリクラスター間の同期を実行します。コマンドを実行しなくとも、一定間隔でリポジトリクラスター間の差分はチェックされており(ここでは10分間隔)、差分を検知すれば同期は行われます。

# リポジトリ・クラスター間の同期
$ flux reconcile kustomization flux-system --with-source
(中略)
✔ Kustomization reconciliation completed
✔ applied revision main/4301e2dda8423ab580df8ef5d025f9a278cb7664


# podinfoアプリがデプロイされる
$ kubectl get pods
NAME                      READY   STATUS    RESTARTS   AGE
podinfo-c598c9677-v9rvq   1/1     Running   0          20s


# この時点ではコンテナイメージタグは5.0.0
$ kubectl get deployment podinfo -oyaml | grep 'image:'
                f:image: {}
        image: ghcr.io/stefanprodan/podinfo:5.0.0

次にコンテナレジストリを定義する ImageRepository 、同期するコンテナイメージの指定を行う ImagePolicy というリソースを作成します。今回指定する ImageRepository には、複数のバージョンのイメージが保存されています。ここで指定する 5.0.x の範囲では、 5.0.3 のタグが付与されたイメージが最新のバージョンとなります。

# ImageRegistryリソースのマニフェスト生成
$ flux create image repository podinfo \
> --image=ghcr.io/stefanprodan/podinfo \
> --interval=1m \
> --export > ./clusters/my-cluster/podinfo-registry.yaml


# ImagePolicyリソースのマニフェスト生成
$ flux create image policy podinfo \
> --image-ref=podinfo \
> --select-semver=5.0.x \
> --export > ./clusters/my-cluster/podinfo-policy.yaml


# コミット
$ git add . && git commit -m "add podinfo image scan"
$ git push

podinfo-registry.yaml

---
apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImageRepository
metadata:
  name: podinfo
  namespace: flux-system
spec:
  image: ghcr.io/stefanprodan/podinfo
  interval: 1m0s

podinfo-policy.yaml

---
apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImagePolicy
metadata:
  name: podinfo
  namespace: flux-system
spec:
  imageRepositoryRef:
    name: podinfo
  policy:
    semver:
      range: 5.0.x

ここでも flux reconcile コマンドを実行し、リポジトリクラスター間の同期を手動で実行します。

# リポジトリ・クラスター間の同期
$ flux reconcile kustomization flux-system --with-source
(中略)
✔ Kustomization reconciliation completed
✔ applied revision main/242b5514ff60e0c02a29b95564fc48a6e75c0ac0


# リソースの確認
$ kubectl get imagerepository -n flux-system
NAME      LAST SCAN              TAGS
podinfo   2021-07-18T02:27:06Z   19

$ kubectl get imagepolicy -n flux-system
NAME      LATESTIMAGE
podinfo   ghcr.io/stefanprodan/podinfo:5.0.3

なお、リソースの状態は flux get コマンドなどでも確認できます。

$ flux get image repository
NAME    READY   MESSAGE                         LAST SCAN                       SUSPENDED
podinfo True    successful scan, found 19 tags  2021-07-18T11:28:06+09:00       False

$ flux get image policy
NAME    READY   MESSAGE                                                                 LATEST IMAGE                    
podinfo True    Latest image tag for 'ghcr.io/stefanprodan/podinfo' resolved to: 5.0.3  ghcr.io/stefanprodan/podinfo:5.0.3

次に podinfo-deployment.yaml を一部修正します。コンテナイメージの自動更新を行う ImageUpdateAutomation というリソースは、マニフェスト中の特定のマーカーを目印にして、イメージタグの書き換えを行います。

※参考リンク:

$ vi clusters/my-cluster/podinfo-deployment.yaml

# 以下のように修正
    spec:
      containers:
      - name: podinfod
        image: ghcr.io/stefanprodan/podinfo:5.0.0 # {"$imagepolicy": "flux-system:podinfo"}
        imagePullPolicy: IfNotPresent

次に ImageUpdateAutomation リソースを作成し、コンテナイメージの自動更新を実行可能にします。

# ImageUpdateAutomationリソースのマニフェスト生成
$ flux create image update flux-system \
> --git-repo-ref=flux-system \
> --git-repo-path="./clusters/my-cluster" \
> --checkout-branch=main \
> --push-branch=main \
> --author-name=fluxcdbot \
> --author-email=fluxcdbot@users.noreply.github.com \
> --commit-template="{{range .Updated.Images}}{{println .}}{{end}}" \
> --export > ./clusters/my-cluster/flux-system-automation.yaml


# コミット
$ git add . && git commit -m "add image updates automation" && git push

flux-system-automation.yaml

---
apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImageUpdateAutomation
metadata:
  name: flux-system
  namespace: flux-system
spec:
  git:
    checkout:
      ref:
        branch: main
    commit:
      author:
        email: fluxcdbot@users.noreply.github.com
        name: fluxcdbot
      messageTemplate: '{{range .Updated.Images}}{{println .}}{{end}}'
    push:
      branch: main
  interval: 1m0s
  sourceRef:
    kind: GitRepository
    name: flux-system
  update:
    path: ./clusters/my-cluster
    strategy: Setters

しばらくすると、以下のように ImageUpdateAutomation の作成が確認できます。

$ kubectl get imageupdateautomation -n flux-system
NAME          LAST RUN
flux-system   2021-07-18T02:46:40Z

$ flux get image update
NAME            READY   MESSAGE                                                                 LAST RUN                        SUSPENDED
flux-system     True    committed and pushed 614e62d52ec55844f61fcd7f5d67999757229729 to main   2021-07-18T11:45:33+09:00       False

マニフェストファイルを確認すると、イメージタグが 5.0.3 に更新されており、Podのイメージも更新されていることがわかります。

$ git pull
$ cat clusters/my-cluster/podinfo-deployment.yaml | grep "image:"
          image: ghcr.io/stefanprodan/podinfo:5.0.3 # {"$imagepolicy": "flux-system:podinfo"}


$ kubectl get deploy podinfo -oyaml | grep 'image:'
                f:image: {}
        image: ghcr.io/stefanprodan/podinfo:5.0.3


# イメージ関連のリソースの状態を一括で確認できる
$ flux get images all
NAME                    READY   MESSAGE                         LAST SCAN                       SUSPENDED
imagerepository/podinfo True    successful scan, found 19 tags  2021-07-18T11:51:18+09:00       False

NAME                    READY   MESSAGE                                                                 LATEST IMAGE    
imagepolicy/podinfo     True    Latest image tag for 'ghcr.io/stefanprodan/podinfo' resolved to: 5.0.3  ghcr.io/stefanprodan/podinfo:5.0.3

NAME                                    READY   MESSAGE                                                         LAST RUN                        SUSPENDED
imageupdateautomation/flux-system       True    no updates made; last commit 614e62d at 2021-07-18T02:45:33Z    2021-07-18T11:50:54+09:00       False

イメージ更新の停止

flux suspend コマンドはコンテナイメージの同期を一時的に停止するコマンドです。例えばあるイメージのバージョンに問題があり、一時的に同期を停止したい場合などに利用できます。停止後は flux resume コマンドにより、同期を再開することができます。

$ flux suspend image update flux-system
► suspending image update automation flux-system in flux-system namespace
✔ image update automation suspended


$ flux get image update
NAME            READY   MESSAGE                                                         LAST RUN                       SUSPENDED
flux-system     True    no updates made; last commit 614e62d at 2021-07-18T02:45:33Z    2021-07-18T11:53:01+09:00      True


$ flux resume image update flux-system
► resuming image update automation flux-system in flux-system namespace
✔ image update automation resumed
◎ waiting for ImageUpdateAutomation reconciliation
✔ ImageUpdateAutomation reconciliation completed
✔ no updates made; last commit 614e62d at 2021-07-18T02:45:33Z

コンテナイメージのロールバック

コンテナイメージを最新のバージョンから変更したい場合、 ImagePolicy で指定するイメージタグを修正することで、イメージのロールバックを実行することができます。

# 変更前のバージョン
$ kubectl describe imagepolicy podinfo -n flux-system

(中略)
Spec:
  Image Repository Ref:
    Name:  podinfo
  Policy:
    Semver:
      Range:  5.0.x


# イメージタグの変更
$ flux create image policy podinfo \
> --image-ref=podinfo \
> --select-semver=5.0.0
✚ generating ImagePolicy
► applying ImagePolicy
✔ ImageRepository updated
◎ waiting for ImagePolicy reconciliation
✔ ImagePolicy reconciliation completed


# 更新後のイメージタグ
$ kubectl describe imagepolicy podinfo -n flux-system | grep Semver -3
  Image Repository Ref:
    Name:  podinfo
  Policy:
    Semver:
      Range:  5.0.0
Status:
  Conditions:


# Podのイメージも変更されている
$ kubectl get deploy podinfo -oyaml | grep "image:"
                f:image: {}
        image: ghcr.io/stefanprodan/podinfo:5.0.0

なお、コンテナイメージの同期に利用できるレジストリは、DockerHubやGitHub Container Registryのほか、Amazon Elastic Container Registry (ECR) やAzure Container Registry (ACR)など、パブリッククラウドのサービスも利用可能です。ただし、これらはレジストリのアクセストークンの期限が一定時間で期限切れとなるため、それを更新するためのCronJobの利用方法が紹介されています

通知

次にFluxからの通知を試してみます。今回はSlackに通知を飛ばすよう設定します。

※参考リンク:

作業を行う前提として、SlackのIncoming Webhookを有効にし、Webhook URLを取得しておきます。Webhook URLの情報はSecretリソースとして登録しておきます。

# Slack Webhook URLの登録
$ kubectl -n flux-system create secret generic slack-url \
> --from-literal=address=<Slack Incoming Webhook URL>


$ kubectl get secret -n flux-system
NAME                                      TYPE                                  DATA   AGE
default-token-649ts                       kubernetes.io/service-account-token   3      117m
flux-system                               Opaque                                3      25m
helm-controller-token-cdrqn               kubernetes.io/service-account-token   3      117m
image-automation-controller-token-8nrpz   kubernetes.io/service-account-token   3      76m
image-reflector-controller-token-p96zm    kubernetes.io/service-account-token   3      76m
kustomize-controller-token-k2zft          kubernetes.io/service-account-token   3      117m
notification-controller-token-z4npl       kubernetes.io/service-account-token   3      117m
slack-url                                 Opaque                                1      15s
source-controller-token-ssh2v             kubernetes.io/service-account-token   3      117m

次にFluxで通知機能を利用するための Provider Alert というリソースを作成します。 Provider は通知先となるプロバイダーを、 Alertは通知の対象となるリソースや通知レベルの設定などを定義します。

# Providerリソースのマニフェスト生成
$ flux create alert-provider slack \
> --type slack \
> --channel fluxcd-test \
> --secret-ref slack-url \
> --export > ./clusters/my-cluster/slack-alert-provider.yaml


# Alertリソースのマニフェスト生成
$ flux create alert flux-system \
> --event-severity info \
> --event-source Kustomization/*,GitRepository/*,ImagePolicy/* \
> --provider-ref slack \
> --export > ./clusters/my-cluster/slack-alert.yaml


# コミット
$ git add . && git commit -m "add alert provider & alert" && git push

slack-alert-provider.yaml

---
apiVersion: notification.toolkit.fluxcd.io/v1beta1
kind: Provider
metadata:
  name: slack
  namespace: flux-system
spec:
  channel: fluxcd-test
  secretRef:
    name: slack-url
  type: slack

slack-alert.yaml

---
apiVersion: notification.toolkit.fluxcd.io/v1beta1
kind: Alert
metadata:
  name: flux-system
  namespace: flux-system
spec:
  eventSeverity: info
  eventSources:
  - kind: Kustomization
    name: '*'
  - kind: GitRepository
    name: '*'
  - kind: ImagePolicy
    name: '*'
  providerRef:
    name: slack

flux reconcile コマンドにより、リポジトリクラスター間を同期します。

$ flux reconcile kustomization flux-system --with-source


# リソースの状態確認
$ kubectl get provider -n flux-system
NAME    READY   STATUS        AGE
slack   True    Initialized   100s

$ kubectl get alert -n flux-system
NAME          READY   STATUS        AGE
flux-system   True    Initialized   6m55s

$ flux get alert-providers
NAME    READY   MESSAGE
slack   True    Initialized

$ flux get alerts
NAME            READY   MESSAGE         SUSPENDED
flux-system     True    Initialized     False

通知設定が完了したので、リソースの変更を行います。ここでは、先ほど変更した ImagePolicy のイメージタグを変更してみます。

$ flux create image policy podinfo \
> --image-ref=podinfo \
> --select-semver=5.0.x

上記設定からしばらくすると、Slackのほうに通知が飛ぶ様子が確認できます。

f:id:FY0323:20210719131304j:plain

Fluxのアンインストール

検証が完了したので、Fluxをアンインストールします。

$ flux uninstall --namespace=flux-system
? Are you sure you want to delete Flux and its custom resource definitions? [y/N] y
(中略)
✔ uninstall finished


# 削除の確認
$ kubectl get ns
NAME              STATUS   AGE
default           Active   12h
kube-node-lease   Active   12h
kube-public       Active   12h
kube-system       Active   12h

Veleroに入門する

今回はKubernetesリソースのバックアップ・リストアを実現するVeleroを使ってみます。

Cloud Nativeな世界のバックアップ・リストア

Veleroを動かす前に、バックアップ・リストアについていくつか書いておきます。

そもそもバックアップ・リストアを実現する理由としては、大きく以下の3つの理由があるかと思います。

  • DR: システムの稼働する主要サイトで災害等が発生し、システムとしての機能を失ったときに、別のサイトにてシステムを再稼働させる。
  • オペレーションのリカバリ: 業務の中でデータの紛失や論理的破壊が発生したときに、バックアップデータから対象のデータを復旧する。
  • アーカイブ: 法規制などに対応するため長期的なデータ保存が必要となるときに、バックアップデータを使用する。

また、これらの目的を達成することに加え、「Cloud Nativeな世界でのバックアップ・リストア」という観点で調べてみると、以下のような観点を合わせて検討することが重要となるようです。

サーバーでなくワークロード単位でバックアップ

Cloud Nativeな世界では、アーキテクチャ疎結合化により、それにかかわる組織やチームも疎結合となり、各チームが自律的に動けることを目指します。自律的なチームが複数に分かれると、各チームはそれぞれ独自のデータやワークロードをもつことになり、これらが分散して存在することになります。そのため、これまでの、データが一極集中管理されたシステムではなく、各チームごとにデータを所持するシステムでは、サーバー単位でなくワークロード単位でのバックアップが求められます。

例えば1つのKubernetesクラスター上で複数チームがワークロードを載せている場合、クラスターやNode単位でバックアップを取ると、バックアップデータには自分たちのワークロード以外のものも多く含まれ、そこから必要なものを探す手間が発生します。またリストアの際にクラスター・Node単位で実行するとなると、他のチームへの影響も発生し、組織全体のパフォーマンスが低下する要因となりえます。ワークロード単位でバックアップを取ることで、必要なものを見つけやすく、また自チーム内にのみ影響範囲を抑えることが可能となります。

データに着目してバックアップする

現在はCloud Nativeに関わらずインフラリソースのIaC化が進んでおり、必要なリソースはIaCファイルから作成することができます。バックアップを取得する際も、インフラリソースは定義ファイルから再現できるため、重要度は高くありません。一方で、そこに含まれる動的なデータは復元が難しいため、バックアップデータとして重要度が高くなります。

例としては以下のようなものが挙げられます。

  • ソースコードやビルドによる成果物など
  • アプリケーションの設定情報
  • インフラリソースの「状態」:インスタンス数やポート番号など
  • アプリ起動中に保存されたデータを含むデータベース
  • オブジェクトストレージに保存されたファイル

その他

その他、Cloud Nativeな環境で利用するバックアップ・リストアツールには、以下のような要素が含まれているとなお良いでしょう。

  • 複数環境・プロバイダーへの汎用性: オンプレやクラウドなど複数の環境で動作するシステムに対し、汎用的なツールを利用することで、不要な学習コストの削減やリストアのスピードアップにつながるでしょう。一方で、一つのクラウドプロバイダーのみを利用する予定の場合、各プロバイダーの提供するバックアップソリューションを利用することが、最も手軽かつ安全な場合もあります。

  • スケーラビリティ: システムの構成や規模が動的に変化するCloud Nativeなアーキテクチャでは、システムの構成変更に追従する、あるいは新規追加が容易なツールであるほど利用価値が高いといえるでしょう。


※参考ドキュメント:


バックアップ・リストアで考えること

バックアップ・リストアを設計・実装するうえで考えなければならないことは色々とあります。以下にその一例を記載します。

  • 利用目的
  • 取得頻度
  • バックアップ手法
  • 保存場所
  • 保存期間
  • 対象リソース

これらの観点に対して、Veleroはどのように対応しているかを見ていきます。

利用目的

バックアップ・リストアは、その利用目的を明確にすることが重要です。バックアップ・リストアを用いる理由は既に記載しましたが、Veleroでは以下のような用途を想定しています(Veleroドキュメントより)。

取得頻度

バックアップデータをどのような頻度で取得するかは、バックアップの利用目的によって変わります。作業前にバックアップデータを取得する、スケジュール実行で1日1回取得する、など、複数のタイミングでデータ取得を実行できる必要があります。

Veleroではコマンドからバックアップ・リストアを実行することができます。また velero schedule コマンドにより、Cron形式で指定したスケジュール実行も可能です。

バックアップ手法

バックアップの取得方式としては、以下のようなものがあります。

  • 完全バックアップ:バックアップ対象のすべてのデータをバックアップする
  • 増分バックアップ:前回のバックアップ以降に更新されたデータのみをバックアップする
  • 差分バックアップ:前回の完全バックアップ以降に更新されたデータのみをバックアップする

Veleroでは明確にどの手法か書かれてはいないように見えますが、実際に試したところでは完全バックアップしか提供していないように見えます。

保存場所

Veleroではオブジェクトストレージバケットをバックアップ・リストアデータの保存先として利用できます。オンプレ・クラウドどちらも利用可能です。

保存期間

Veleroではバックアップデータの保存期間は --ttl で設定することができます(Veleroドキュメントより)。デフォルトの保存期間は30日間です。またオブジェクトストレージに保存されたデータが削除されると、Veleroがそれを検知・同期して、クラスター上のバックアップデータを削除する動きになります。

対象リソース

VeleroではKubernetesクラスターリソース及びPersistent Volumeが保存対象です。Podなどのワークロードはjson形式で保存します。

PersistentVolumeでクラウドプロバイダーの提供するブロックストレージを利用している場合、PersistentVolumeに格納されたデータをスナップショットとして保存します。

Velero docs - Output file format

Veleroの紹介

構成

veleroの構成は以下の図のようになります。Veleroでは BackupController というControllerが中心となってバックアップ・リストア機能を提供します。Backup というCustom Resourceが作成されると、 BackupControllerKubernetes APIにクエリを投げてバックアップデータを収集し、オブジェクトストレージへバックアップデータのアップロードを行います。

velero

Veleroドキュメントより

Veleroを使ってみる

ここからVeleroを実際に動かしてみます。利用した環境は以下の通りです。

Amazon EKS上でVeleroを動かす際は、EKS Workshopのページに手順がコンパクトにまとまっているので楽です。

www.eksworkshop.com

Veleroを利用するには、 velero CLIのインストールと、クラスター上へのController等リソースの配置が必要になります。CLIのインストールはこちらのページから行い、Controllerなどのデプロイは、マニフェストファイルを用意するほか、velero CLIからも実行することが可能です。今回はCLIを利用する方法で準備を行いました。

$ velero install \
>     --provider aws \
>     --plugins velero/velero-plugin-for-aws:v1.1.0 \
>     --bucket velero-backup-20210523 \
>     --backup-location-config region=ap-northeast-1 \
>     --snapshot-location-config region=ap-northeast-1 \
>     --secret-file ./credentials-velero

$ kubectl get all -n velero
NAME                          READY   STATUS    RESTARTS   AGE
pod/velero-679457dc45-hfn9f   1/1     Running   0          101s

NAME                     READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/velero   1/1     1            1           101s

NAME                                DESIRED   CURRENT   READY   AGE
replicaset.apps/velero-679457dc45   1         1         1       101s

今回はAWSを利用するため --plugins velero/velero-plugin-for-aws:v1.1.0というオプションを指定しています。指定するバージョンはこちらのページを見て変更してください。また --secret-file で指定したファイルには、AWSにアクセスするのに必要な秘匿情報を記載しています。

※その他の利用可能なプロバイダーはこちらのページに記載されています。

Veleroのインストールが完了したので、バックアップの取得とリストアを試します。今回はEKSクラスターをもう一つ作成し、異なるクラスター上に同じワークロードを作成できるか試しました。

バックアップの取得

# バックアップ対象
$ kubectl get pods
NAME             READY   STATUS              RESTARTS   AGE
velero-testpod   1/1     Running             0          29s

$ kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                STORAGECLASS   REASON   AGE
pvc-4b04c94b-c9bd-4206-8806-0e783b5ccce4   8Gi        RWO            Delete           Bound    default/velero-pvc   gp2                     2m45s

$ kubectl get pvc
NAME         STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
velero-pvc   Bound    pvc-4b04c94b-c9bd-4206-8806-0e783b5ccce4   8Gi        RWO            gp2            81s



# バックアップの作成
$ velero backup create velero-backup-`date +%Y%m%d%H%M`
Backup request "velero-backup-202105230844" submitted successfully.
Run `velero backup describe velero-backup-202105230844` or `velero backup logs velero-backup-202105230844` for more details.



# バックアップ作成結果の確認
$ velero backup describe velero-backup-202105230844
Name:         velero-backup-202105230844
Namespace:    velero
Labels:       velero.io/storage-location=default
Annotations:  velero.io/source-cluster-k8s-gitversion=v1.19.8-eks-96780e
              velero.io/source-cluster-k8s-major-version=1
              velero.io/source-cluster-k8s-minor-version=19+

Phase:  Completed

Errors:    0
Warnings:  0

Namespaces:
  Included:  *
  Excluded:  <none>

Resources:
  Included:        *
  Excluded:        <none>
  Cluster-scoped:  auto

Label selector:  <none>

Storage Location:  default

Velero-Native Snapshot PVs:  auto

TTL:  720h0m0s

Hooks:  <none>

Backup Format Version:  1.1.0

Started:    2021-05-23 08:44:43 +0900 JST
Completed:  2021-05-23 08:44:50 +0900 JST

Expiration:  2021-06-22 08:44:43 +0900 JST

Total items to be backed up:  353
Items backed up:              353

Velero-Native Snapshots:  1 of 1 snapshots completed successfully (specify --details for more information)

クラスターの用意

# 別クラスターの用意
$ eksctl create cluster -f eks-clusterconfig-2.yml

$ eksctl get cluster
2021-05-23 10:18:20 [ℹ]  eksctl version 0.40.0
2021-05-23 10:18:20 [ℹ]  using region ap-northeast-1
NAME            REGION          EKSCTL CREATED
eks-cluster     ap-northeast-1  True
eks-cluster-2   ap-northeast-1  True★

# 別クラスターでのVeleroインストール
# バックアップデータの保存されているオブジェクトストレージを指定
$ velero install \
>     --provider aws \
>     --plugins velero/velero-plugin-for-aws:v1.1.0 \
>     --bucket velero-backup-20210523 \
>     --backup-location-config region=ap-northeast-1 \
>     --snapshot-location-config region=ap-northeast-1 \
>     --secret-file ./credentials-velero

# 作成したバックアップが確認できる
$ velero backup get
NAME                         STATUS      ERRORS   WARNINGS   CREATED                         EXPIRES   STORAGE LOCATION   SELECTOR
velero-backup-202105230844   Completed   0        0          2021-05-23 08:44:43 +0900 JST   29d       default            <none>

リストアによるリソース作成

# リストアの実行
$ velero restore create --from-backup velero-backup-202105230844
Restore request "velero-backup-202105230844-20210523095302" submitted successfully.
Run `velero restore describe velero-backup-202105230844-20210523095302` or `velero restore logs velero-backup-202105230844-20210523095302` for more details.


# リストア結果の確認
$ velero restore describe velero-backup-202105230844-20210523095302
Name:         velero-backup-202105230844-20210523095302
Namespace:    velero
Labels:       <none>
Annotations:  <none>

Phase:  Completed

Started:    2021-05-23 09:53:13 +0900 JST
Completed:  2021-05-23 09:53:44 +0900 JST

Warnings:
  Velero:     <none>
  Cluster:  could not restore, customresourcedefinitions.apiextensions.k8s.io "backups.velero.io" already exists. Warning: the in-cluster version is different than the backed-up version.
            could not restore, customresourcedefinitions.apiextensions.k8s.io "backupstoragelocations.velero.io" already exists. Warning: the in-cluster version is different than the backed-up version.
            could not restore, customresourcedefinitions.apiextensions.k8s.io "deletebackuprequests.velero.io" already exists. Warning: the in-cluster version is different than the backed-up version.
            could not restore, customresourcedefinitions.apiextensions.k8s.io "downloadrequests.velero.io" already exists. Warning: the in-cluster version is different than the backed-up version.
            could not restore, customresourcedefinitions.apiextensions.k8s.io "podvolumebackups.velero.io" already exists. Warning: the in-cluster version is different than the backed-up version.
            could not restore, customresourcedefinitions.apiextensions.k8s.io "podvolumerestores.velero.io" already exists. Warning: the in-cluster version is different than the backed-up version.
            could not restore, customresourcedefinitions.apiextensions.k8s.io "resticrepositories.velero.io" already exists. Warning: the in-cluster version is different than the backed-up version.
            could not restore, customresourcedefinitions.apiextensions.k8s.io "restores.velero.io" already exists. Warning: the in-cluster version is different than the backed-up version.
            could not restore, customresourcedefinitions.apiextensions.k8s.io "schedules.velero.io" already exists. Warning: the in-cluster version is different than the backed-up version.
            could not restore, customresourcedefinitions.apiextensions.k8s.io "serverstatusrequests.velero.io" already exists. Warning: the in-cluster version is different than the backed-up version.
            could not restore, customresourcedefinitions.apiextensions.k8s.io "volumesnapshotlocations.velero.io" already exists. Warning: the in-cluster version is different than the backed-up version.
            could not restore, mutatingwebhookconfigurations.admissionregistration.k8s.io "pod-identity-webhook" already exists. Warning: the in-cluster version is different than the backed-up version.
            could not restore, mutatingwebhookconfigurations.admissionregistration.k8s.io "vpc-resource-mutating-webhook" already exists. Warning: the in-cluster version is different than the backed-up version.
            could not restore, validatingwebhookconfigurations.admissionregistration.k8s.io "vpc-resource-validating-webhook" already exists. Warning: the in-cluster version is different than the backed-up version.
  Namespaces:
    default:      could not restore, endpoints "kubernetes" already exists. Warning: the in-cluster version is different than the backed-up version.
                  could not restore, endpointslices.discovery.k8s.io "kubernetes" already exists. Warning: the in-cluster version is different than the backed-up version.
                  could not restore, services "kubernetes" already exists. Warning: the in-cluster version is different than the backed-up version.
    kube-system:  could not restore, configmaps "aws-auth" already exists. Warning: the in-cluster version is different than the backed-up version.
                  could not restore, configmaps "cp-vpc-resource-controller" already exists. Warning: the in-cluster version is different than the backed-up version.
                  could not restore, configmaps "eks-certificates-controller" already exists. Warning: the in-cluster version is different than the backed-up version.
                  could not restore, configmaps "extension-apiserver-authentication" already exists. Warning: the in-cluster version is different than the backed-up version.
                  could not restore, configmaps "kube-proxy" already exists. Warning: the in-cluster version is different than the backed-up version.
                  could not restore, endpoints "kube-controller-manager" already exists. Warning: the in-cluster version is different than the backed-up version.
                  could not restore, endpoints "kube-dns" already exists. Warning: the in-cluster version is different than the backed-up version.
                  could not restore, endpoints "kube-scheduler" already exists. Warning: the in-cluster version is different than the backed-up version.
                  could not restore, leases.coordination.k8s.io "kube-controller-manager" already exists. Warning: the in-cluster version is different than the backed-up version.
                  could not restore, leases.coordination.k8s.io "kube-scheduler" already exists. Warning: the in-cluster version is different than the backed-up version.
                  could not restore, services "kube-dns" already exists. Warning: the in-cluster version is different than the backed-up version.

Backup:  velero-backup-202105230844

Namespaces:
  Included:  all namespaces found in the backup
  Excluded:  <none>

Resources:
  Included:        *
  Excluded:        nodes, events, events.events.k8s.io, backups.velero.io, restores.velero.io, resticrepositories.velero.io
  Cluster-scoped:  auto

Namespace mappings:  <none>

Label selector:  <none>

Restore PVs:  auto


$ kubectl get pods
NAME             READY   STATUS    RESTARTS   AGE
velero-testpod   1/1     Running   0          22m
$ kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                STORAGECLASS   REASON   AGE
pvc-4b04c94b-c9bd-4206-8806-0e783b5ccce4   8Gi        RWO            Delete           Bound    default/velero-pvc   gp2                     23m
$ kubectl get pvc
NAME         STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
velero-pvc   Bound    pvc-4b04c94b-c9bd-4206-8806-0e783b5ccce4   8Gi        RWO            gp2            23m

取得頻度

velero schedule コマンドを実行すると、Cron形式で指定したサイクルでバックアップを取得することができます。

$ velero schedule create velero-backup-shcedule-`date +%Y%m%d` --schedule="*/10 * * * *"
Schedule "velero-backup-shcedule-20210523" created successfully.

# 初回バックアップの取得
$ velero backup get
NAME                                             STATUS      ERRORS   WARNINGS   CREATED                         EXPIRES   STORAGE LOCATION   SELECTOR
velero-backup-shcedule-20210523-20210523013142   Completed   0        0          2021-05-23 10:31:42 +0900 JST   30d       default            <none>

# 10分後
$ velero backup get
NAME                                             STATUS      ERRORS   WARNINGS   CREATED                         EXPIRES   STORAGE LOCATION   SELECTOR
velero-backup-shcedule-20210523-20210523014027   Completed   0        0          2021-05-23 10:40:27 +0900 JST   29d       default            <none>
velero-backup-shcedule-20210523-20210523013142   Completed   0        0          2021-05-23 10:31:42 +0900 JST   29d       default            <none>

対象リソース

Veleroのバックアップには複数のオプションがあります。以下のコマンドでは、指定のNamespace上にあるリソースのうち、Secret リソースをバックアップ対象から除外しています。

$ velero backup create velero-backup-exclude-`date +%Y%m%d%H%M%S` --include-namespaces default --exclude-resources secret

$ velero backup describe velero-backup-exclude-20210523112153

<中略>

Namespaces:
  Included:  default★
  Excluded:  <none>

Resources:
  Included:        *
  Excluded:        secret★
  Cluster-scoped:  auto

Secret リソースには秘匿情報が記載されており、このデータをストレージ上に保管することがセキュリティリスクとなる可能性があります。そのため、上記コマンドのような形でリソースを除外し、セキュリティリスクを軽減することが可能です。

保存期間

バックアップ取得時は --ttl オプションで保存期間を指定することができます。指定した保存期間を経過すると、そのバックアップデータは削除対象となり、しばらくすると削除されます。

$ velero backup create velero-backup-20210529-ttl-minutes --ttl 0h1m0s

$ velero backup get
NAME                                 STATUS      ERRORS   WARNINGS   CREATED                         EXPIRES   STORAGE LOCATION   SELECTOR
velero-backup-20210529               Completed   0        0          2021-05-29 09:21:55 +0900 JST   29d       default            <none>
velero-backup-20210529-ttl-minutes   Completed   0        0          2021-05-29 09:28:42 +0900 JST   43m ago   default            <none>

$ velero backup get
NAME                         STATUS      ERRORS   WARNINGS   CREATED                         EXPIRES   STORAGE LOCATION   SELECTOR
velero-backup-20210529       Completed   0        0          2021-05-29 09:21:55 +0900 JST   29d       default            <none>

$

またバックアップデータを保存しているオブジェクトストレージ側のデータを削除すると、クラスター上の backup リソースも削除されます。

$ velero backup create velero-backup-20210529-object-storage-test

$ velero backup get
NAME                                         STATUS      ERRORS   WARNINGS   CREATED                         EXPIRES   STORAGE LOCATION   SELECTOR
velero-backup-20210529                       Completed   0        0          2021-05-29 09:21:55 +0900 JST   29d       default            <none>
velero-backup-20210529-object-storage-test   Completed   0        0          2021-05-29 10:10:09 +0900 JST   30d       default            <none>


# S3バケットの削除
$ aws s3 rm s3://velero-backup-20210523/backups/velero-backup-20210529-object-storage-test --recursive
$ aws s3 rm s3://velero-backup-20210523/backups/velero-backup-20210529-object-storage-test


# Backupが削除される
$ velero backup get
NAME                                 STATUS      ERRORS   WARNINGS   CREATED                         EXPIRES   STORAGE LOCATION   SELECTOR
velero-backup-20210529               Completed   0        0          2021-05-29 09:21:55 +0900 JST   29d       default            <none>

※参考ドキュメント:


その他

その他、Veleroを利用するうえで気を付けたほうがよさそうな点についても書き残しておきます。

  • Veleroのバックアップは厳密にはatomicでないため、バックアップ中に作成されたリソースがデータに含まれない場合もあります(Veleroドキュメントより)

  • バックアップ前にPodでコマンドを実行したい場合もあると思います(例:DBでインメモリデータをディスクに吐き出すなど)が、その場合はbackup hooksを利用して、バックアップデータ取得前に特定のコマンドを実行することができます。

  • Veleroでは現在、複数ロケーションに同時にバックアップ・スナップショットを保存することはできません。一方でスケジュール実行で場所だけを変更して、複数ロケーションへの保存を実現することはできるようです(Veleroドキュメントより)。

  • バックアップデータの容量という観点から、重複排除の機能があるか、という点も重要となるかと思います。Veleroのドキュメントからは、重複排除の有無を見つけることができませんでしたが、resticでは重複排除に言及した部分があるため、内部的には実装しているのかもしれません。

参考ドキュメント

【メモ】CircleCIとAmazon ECR / EKSを利用したCI/CDパイプラインの例

はじめに

今回はCircleCIとAmazon ECRを利用したCI/CDパイプラインを用意し、コンテナイメージのビルドからKubernetesクラスタへのデプロイまで実行するサンプルを作成しました。

構成

今回は以下のようなCI/CDパイプラインを作成しました。

構成図は以下のようになります。CI/CDパイプラインはCircleCI、コンテナレジストリAmazon ECR、Kubernets環境はAmazon EKSを選択しています。

f:id:FY0323:20210515172615j:plain

CircleCI

今回利用したリポジトリディレクトリ構造はこちら。.circleci/config.yml 以外は前回のGitHub Actionsの例と同じファイルを使用しています。

.
├── .circleci
│   └── config.yml
├── README.md
├── app
│   ├── dockerfile
│   ├── main.go
│   └── main_test.go
└── manifest
    ├── deployment.yaml
    └── test.sh

CI/CDパイプラインは以下のようなファイルで構成しています。CircleCIを利用するには .circleci/config.yml ファイル上にワークフローを定義する必要があります。

今回はAmazon ECRへコンテナイメージをPushするために circleci/aws-ecrというOrbを利用しています。このOrbは、サンプルなどを見る限りWorkflow上で定義することが多いのですが、今回処理結果をSlackに通知したかったためecr-pushというJob上で定義しています。本当はJobの実行基盤をDockerにしたかったのですが上手くいかなかったため、ecr-push JobはVM上で実行しています。

./circleci/config.yml

version: 2.1
 
orbs:
  slack: circleci/slack@4.4.2
  aws-ecr: circleci/aws-ecr@7.0.0
  aws-eks: circleci/aws-eks@1.1.0
  kubernetes: circleci/kubernetes@0.12.0
 
commands:
  slack_fail:
    steps:
      - slack/notify:
          event: fail
          template: basic_fail_1
  slack_success:
    steps:
      - slack/notify:
          event: pass
          template: basic_success_1
 
jobs:
  go-test:
    docker:
      - image: circleci/golang:1.15.6
    working_directory: ~/repo
    steps:
      - checkout
      - run:
          name: Run tests
          command: go test -v ./app
      - slack_fail
  ecr-push:
    machine:
      image: ubuntu-2004:202010-01
    steps:
      - aws-ecr/build-and-push-image:
          account-url: AWS_ECR_ACCOUNT_URL
          aws-access-key-id: AWS_ACCESS_KEY_ID
          aws-secret-access-key: AWS_SECRET_ACCESS_KEY
          dockerfile: dockerfile
          path: ./app
          region: AWS_REGION
          repo: <repository-name>
          tag: latest
      - slack_fail
      - slack_success
 
  k8s-validate:
    docker:
      - image: circleci/golang:1.15.6
    working_directory: ~/repo
    steps:
      - checkout
      - run:
          name: install
          command: |
            wget https://github.com/instrumenta/kubeval/releases/latest/download/kubeval-linux-amd64.tar.gz
            tar xf kubeval-linux-amd64.tar.gz
            sudo cp kubeval /usr/local/bin
      - run:
          name: validation
          command: kubeval ./manifest/*.yaml
      - slack_fail
  k8s-deployment:
    executor: aws-eks/python3
    parameters:
      cluster-name:
        default: <cluster-name>
        type: string
      aws-region:
        default: <aws-region>
        type: string
    steps:
      - checkout
      - aws-eks/update-kubeconfig-with-authenticator:
          cluster-name: << parameters.cluster-name >>
          aws-region: << parameters.aws-region >>
          install-kubectl: true
      - kubernetes/create-or-update-resource:
          resource-file-path: manifest/deployment.yaml
      - run:
          name: check resource
          command: kubectl get pods
      - run:
          name: test apps
          command: |
            chmod +x ./manifest/test.sh
            sh ./manifest/test.sh
      - slack_fail
      - slack_success
 
 
workflows:
  docker-build-and-deploy:
    jobs:
      - go-test
      - k8s-validate
      - ecr-push:
          requires:
            - go-test
          filters:
            branches:
              only:
                - main
      - k8s-deployment:
          requires:
            - k8s-validate
            - ecr-push
          filters:
            branches:
              only:
                - main


利用したOrbsはこちら。

参考ドキュメントはこちら。


利用時の前提

パイプラインを利用するうえで必要な準備なども記載します。

CircleCIの登録

CircleCIに登録していない場合は登録と利用するリポジトリの選択を済ませておきます。

※参考ドキュメント:

Slackへの通知用設定

Jobの実行結果をSlackに通知するため、Slackの設定を行います。Slack AppにてTokenの取得と投稿用チャンネルの作成をしておきます。Slackでの設定はこちらのドキュメントに書かれた通り実行すれば完了するかと思います。

CircleCIでの変数の設定

CircleCIの Environment Variables には以下の情報を登録します。

  • AWS_ACCESS_KEY_ID: AWSへのアクセスに利用するアカウントID
  • AWS_SECRET_ACCESS_KEY: AWSへのアクセスに利用するシークレットアクセスキー
  • AWS_ECR_ACCOUNT_URL: Amazon ECRへのアクセスに利用するURL
  • AWS_REGION: AWSで利用するリージョン
  • SLACK_ACCESS_TOKEN: Slack Appで取得したアクセスToken
  • SLACK_DEFAULT_CHANNEL: Slack通知に利用するチャンネル名

その他

CircleCIでは、リポジトリ上のディレクトリやファイル毎にJobを起動することができない、と認識しています。そのため、以前GitHub Actionsでつくったパイプラインと異なり、今回はひとつのワークフローの中に、イメージのビルドとクラスターへのデプロイのJobを入れています。

CIとCDのプロセスを分けようと思った場合、いくつか方法はあると考えられます。

アプリ・インフラそれぞれ専用のリポジトリを用意する

リポジトリごとに.circleci/config.ymlを作成し、ワークフローを設定すれば処理を分けることが可能になり、CIとCDを分離することができます。アプリとインフラのコードを別のリポジトリに管理する場合、こちらの方法を採用することができるでしょう。

シェルスクリプト上で処理する

こちらの記事などで紹介されていますが、シェルスクリプト上でCircleCIを実行するタイミングをコントロールする、という方法もあるようです。プロジェクトで利用するコードをmonorepoで管理する場合、こちらの方法を採用することになるでしょう。

【メモ】GitHub ActionsとGitHub Container Registryを利用したCI/CDパイプラインの例

はじめに

今回はタイトルの通り、GItHub ActionsとGitHub Container Registryを利用したCI/CDを用意し、コンテナイメージのビルドからKubernetesクラスターへのデプロイまでを実行するサンプルを作成しました。

構成

CI/CDのパイプラインのパターンはいくつか考えられますが、今回メインで用意したのは、以下のような特徴を備えたものです。

今回の構成は以下のようになります。CI/CDパイプラインはGitHub Actionsで、コンテナイメージのレジストリGitHub Contianer Registryを利用しています。KubernetesクラスターはAmazon EKSを利用していますが、kubectl でアクセスできる環境であれば何でも良いと思います。

f:id:FY0323:20210504141133j:plain

f:id:FY0323:20210504141150j:plain

GitHub Actions

今回のサンプルはこちらのリポジトリに配置しています。

ディレクトリ構造は以下の通り。

.
├── .github
│   └── workflows
│       ├── docker-build-and-push.yaml
│       ├── k8s-deploy.yaml
│       ├── k8s-validate.yaml
│       └── test-and-build-code.yaml
├── README.md
├── app
│   ├── dockerfile
│   ├── main.go
│   └── main_test.go
└── manifest
    ├── deployment.yaml
    └── test.sh

CIは以下のようなアクションを用意しました。コンテナイメージビルド時に ghcr.io を指定して、GitHub Container Registryを利用しています。また secrets.USERNAME という値を指定していますが、この理由については後述します。

test-and-build-code.yaml

name: CI test Go code
on: 
  pull_request:
    branches:
    - main
    paths:
    - 'app/**'

jobs:
  test-and-build:
    runs-on: ubuntu-latest
    steps:
    - name: setup
      uses: actions/setup-go@v1
      with:
        go-version: 1.15
    - name: checkout
      uses: actions/checkout@v2
    - name: go test
      run: |
        go test -v ./app
    - name: notification
      uses: 8398a7/action-slack@v3
      with:
        status: ${{ job.status }}
        fields: repo,message,commit,author,action,eventName,ref,workflow,job,took
      env:
        SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
      if: always() 

docker-build-and-push.yaml

name: CI docker build and push
on: 
  pull_request:
    branches:
    - main
    paths: 
    - 'app/**'
    types: [closed]

jobs:
  docker-build-and-push:
    runs-on: ubuntu-latest
    steps:
    - name: checkout
      uses: actions/checkout@v2
    - name: Set up Docker Builder
      uses: docker/setup-buildx-action@v1
    - name: Log into GitHub Container Registry
      uses: docker/login-action@v1
      with:
        registry: ghcr.io
        username: ${{ github.repository_owner }}
        password: ${{ secrets.CR_PAT }}
    - name: build container image
      uses: docker/build-push-action@v2
      with:
        context: ./app
        file: dockerfile
        push: true
        tags: ghcr.io/${{ secrets.USERNAME }}/sample-app:latest
    - name: notification
      uses: 8398a7/action-slack@v3
      with:
        status: ${{ job.status }}
        fields: repo,message,commit,author,action,eventName,ref,workflow,job,took
      env:
        SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
      if: always() 


使用したActionsはこちら。

参考にしたドキュメントはこちら。


CDは以下のようなアクションを用意しました。

k8s-validate.yaml

name: CD validate Kubernetes manifests
on: 
  pull_request:
    branches:
    - main
    paths:
    - 'manifest/**'
    - 'app/**'

jobs:
  validation:
    name: validate k8s manifest
    runs-on: ubuntu-latest
    steps:
    - name: checkout
      uses: actions/checkout@v2
    - name: validation
      uses: instrumenta/kubeval-action@master
      with:
        files: ./manifest/
        version: 1.18
    - name: notification
      uses: 8398a7/action-slack@v3
      with:
        status: ${{ job.status }}
        fields: repo,message,commit,author,action,eventName,ref,workflow,job,took
      env:
        SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
      if: always() 

k8s-deploy.yaml

name: CD deploy container to Kubernetes
on: 
  pull_request:
    branches:
    - main
    paths:
    - 'manifest/**'
    - 'app/**'
    types: [closed]

jobs:
  deploy-to-EKS:
    name: deploy manifests to k8s
    runs-on: ubuntu-latest
    steps:
    - name: checkout
      uses: actions/checkout@v2
    - name: login to AWS
      uses: aws-actions/configure-aws-credentials@v1
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: ap-northeast-1
    - name: set kubectl
      uses: azure/setup-kubectl@v1
    - name: deploy manifest
      env:
        KUBE_CONFIG: ${{ secrets.KUBE_CONFIG }}
      run: |
        echo "$KUBE_CONFIG" > /tmp/kubeconfig
        export KUBECONFIG=/tmp/kubeconfig
        kubectl apply -f manifest/
        chmod +x manifest/test.sh
        sh ./manifest/test.sh
    - name: notification
      uses: 8398a7/action-slack@v3
      with:
        status: ${{ job.status }}
        fields: repo,message,commit,author,action,eventName,ref,workflow,job,took
      env:
        SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
      if: always() 

Kubernetes Podデプロイ後に実行しているテストスクリプト中で kubectl exec ~ を実行しています。GitHub ActionsではTTYを使用することができないため、 kubectl オプションには -i のみを指定しています。


使用したActionsはこちら。

参考にしたドキュメントはこちら。


利用時の前提条件

上記アクションを利用するうえで必要な設定も書いておきます。

GitHub Container Registryを利用可能にする

GitHub Container Registryは現在パブリックベータで提供されるため、GitHubでFeature Previewで有効にする必要があります。

f:id:FY0323:20210504130938j:plain

またGitHub Container Registryにアクセスするために、GitHubのPersonal Access Tokenを用意する必要があります。

f:id:FY0323:20210504131014j:plain

GitHub Secretsに必要な値を設定する

GitHub Secretsには以下の値を設定しています。

  • AWS_ACCESS_KEY_ID : AWSへのアクセスに利用するアカウントID
  • AWS_SECRET_ACCESS_KEY : AWSへのアクセスに利用するシークレットアクセスキー
  • CR_PAT : GitHub Container Registry用のPersonal Access Token。
  • KUBE_CONFIG : Kubernetesクラスターへアクセスするのに利用する kubeconfig
  • SLACK_WEBHOOK_URL : SlackチャンネルにJobの実行結果を送信するためのIncoming Webhook URL
  • USERNAME : コンテナレジストリへのイメージ格納時に使用

AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY は、利用するクラウドプロバイダーに合わせて変更します。KUBE_CONFIGクラスター作成後の ~/.kube/config ファイルの内容を設定します。

USERNAME については少し補足すると、通常は repository_owner を指定すればGitHubアカウント名を指定することができるのですが、私の作成したアカウント名に大文字が含まれており、このまま利用すると repository name must be lowercase. というエラーが発生します。そのためここでは小文字に直したアカウント名を指定しています。

KubernetesからイメージをPullできるようSecretを設定する

GitHub Container Registryはデフォルトでプライベートレジストリになります。KubernetesクラスターがプライベートレジストリからイメージをPullするには、コンテナレジストリにアクセスするためのSecretリソースを作成し、イメージを利用するPodマニフェスト中でImagePullSecret を設定する必要があります。

私はWindows端末でWSL2を利用しているため、以下のようなコマンドを実行してSecretリソースを作成しました。

# secretリソースの作成
$ kubectl create secret docker-registry regcred \
--docker-server=https://ghcr.io \
--docker-username=fy0323 \
--docker-password=<GitHub_Personal_Access_Token>

secret/regcred created


# 作成後の確認
$ kubectl get secret regcred -oyaml
apiVersion: v1
data:
  .dockerconfigjson: <base64_decoded_data>
kind: Secret
metadata:
  creationTimestamp: "2021-05-02T08:11:01Z"
  managedFields:
  - apiVersion: v1
    fieldsType: FieldsV1
    fieldsV1:
      f:data:
        .: {}
        f:.dockerconfigjson: {}
      f:type: {}
    manager: kubectl-create
    operation: Update
    time: "2021-05-02T08:11:01Z"
  name: regcred
  namespace: default
  resourceVersion: "75368"
  selfLink: /api/v1/namespaces/default/secrets/regcred
  uid: ee113b17-4d53-4e19-b475-887f3acc32a2
type: kubernetes.io/dockerconfigjson

Podマニフェストでは、以下のように spec.imagePullSecrets を指定します。

(中略)
    spec:
      containers:
      - name: app
        image: ghcr.io/fy0323/sample-app:latest
        ports:
        - containerPort: 8080
      imagePullSecrets:
      - name: regcred

その他

今回はCIとCDを別のタイミングで実行するよう設定していますが、CIでコンテナイメージをビルドしたタイミングで、テスト環境などのクラスターに対してデプロイを実行したい場合もあります。その場合は、今回のアクションをベースに、アクションの実行パスやタイミングを変更することで実現できます。

また、それとは別に、本番環境へのデプロイは、指定のレビューアの許可を得ることで実行したい、という場合もあります。GitHub Actionsの Environment の機能を利用することで、これを実現することが可能です。ただしこの機能はパブリックリポジトリでのみ利用可能なので、実際のプロジェクトで利用するには制限が発生しそうです。

f:id:FY0323:20210504133843j:plain