Fluxによるアプリケーションの継続的デプロイ
FluxのAutomated deployment of new container imagesを利用して、Kubernetes上でアプリケーションの継続的デプロイを構成する機会があったのでまとめます。
GitOpsの基本形
GitOpsを採用する場合は下図のデプロイフローが基本形になります。
具体的には以下の流れになります。
- 開発者がアプリケーションリポジトリを更新する。
- CIがアプリケーションをビルドし、新しいDockerイメージをプッシュする。
- 開発者がマニフェストリポジトリを更新する。
- GitOpsが新しいマニフェストをデプロイする。
これはWeaveworksのGuide To GitOpsで説明されている開発フローです。チームの大きさやアプリケーションの特性によっては、以下のような違いが出てくると思います。
- アプリケーションリポジトリのブランチ戦略
- アプリケーションリポジトリの更新を契機にビルドとデプロイを行うか、ビルドのみ行うか
- マニフェストリポジトリとクラスタを自動的に同期させるか、手動で同期するか
- 実行環境(dev/stg/prod)によって自動と手動を使い分ける
継続的デプロイに必要なもの
チームが小さい、あるいは、チームが自己完結している場合は、アプリケーションを更新した契機で開発環境にデプロイされる方が効率的に開発できることがあります。ビジネス上の受入テストまで自動化している場合は本番環境まで自動デプロイすることもあります。これを継続的デプロイ(Continuous Deployment)といいます。下記の記事が参考になります。
継続的デプロイを実現するにはアプリケーションのビルドとデプロイを連続して行う必要があります。Cloud FoundryのようなPaaSを利用するとこのような開発フローを簡単に実現できますが、KubernetesのようなCaaSでは仕組みを作る必要があります。具体的には、以下が自動的に流れる仕組みが必要になります。
- 開発者がアプリケーションリポジトリを更新する。
- CIがアプリケーションをビルドし、新しいDockerイメージをプッシュする。
- (何らかの仕組みで)マニフェストリポジトリにあるマニフェストのイメージタグを書き換える。
- GitOpsが新しいマニフェストをクラスタに適用する。
今のところ、GitOpsで継続的デプロイを実現するにはFluxを利用するか、上記3を自作する必要があります。Argo CDでは実現できません。
Flux automated deployment of new container images
FluxにはAutomated deployment of new container imagesという機能があります。この機能を有効にすると、Dockerレジストリに新しいイメージが存在したらマニフェストのイメージタグを新しいものに書き換えてくれます。ちょうど下図のようなデプロイフローになります。
具体的には以下の流れになります。
- Gitリポジトリからマニフェストを取得する。
- Dockerレジストリにあるイメージをスキャンする。
- 新しいタイムスタンプのイメージが存在する場合は以下を行う。
- 新しいマニフェストをクラスタに適用する。
公式ドキュメントには詳細が書かれていないので、Fluxのログを元に動作を推測しています。間違っていたら教えてください。
Automated deployment of new container imagesを有効にするには、Deploymentに以下のアノテーションを付加します。
metadata: annotations: fluxcd.io/automated: "true"
スキャン対象のイメージタグを正規表現やglobで絞り込むことも可能です。以下に例を示します。
metadata: annotations: fluxcd.io/automated: "true" # appコンテナについて、master-で始まるイメージタグのみ自動更新とする fluxcd.io/tag.app: glob:master-* spec: template: spec: containers: - name: app image: ...
スキャン対象のイメージ名やDockerレジストリを限定することも可能です。詳しくはfluxdの引数リストを参照してください。
下記のリポジトリにデモを置いているので、よかったらご覧ください。
FlaggerによるCanary Release
参考までに、FluxとFlaggerを併用するとCanary Releaseが可能です。アプリケーションリポジトリを更新した契機でデプロイを開始し、一定の基準(例:10%のトラフィックに新しいバージョンを適用して99%が成功ステータスだった場合)を満たしたら全トラフィックを新しいバージョンに切り替える、といったことも可能です。
Flaggerを使うにはIstioなどのサービスメッシュが必要です。また、Kialiなどの可視化ツールがあるとトラフィックの切り替えがよく分かります。詳しくは下記の記事が参考になります。
Fluxによる継続的デプロイの課題
Fluxには以下の制約があるため、実際に運用してみると辛いところがあります。
- デプロイが成功した場合やエラーが発生した場合の通知がない。
- WeaveworksとしてはWeave Cloudを使ってほしいみたいです。
- fluxdのログをelastalertなどで監視してSlackに通知する仕組みを作れば可能です。
- デプロイが成功すると
flux-sync
タグが更新されるので、GitHubでタグの更新をSlackに通知するという方法もあります。 - FlaggerにはSlack通知の機能があります。
- GUIがないので、クラスタで何が起きているのか分かりにくい。
- 同期状態を管理するためにマニフェストリポジトリに
flux-sync
というタグが作成される。flux-sync
タグ契機でCIが実行されないように設定するとよいです。
参考までに、fluxctlコマンドを利用すると以下のようなステータスを表示できます。
% fluxctl --k8s-fwd-ns flux -n develop list-images WORKLOAD CONTAINER IMAGE CREATED develop:deployment/echoserver echoserver gcr.io/google_containers/echoserver '-> 1.10 22 Mar 18 17:30 UTC 1.9 14 Feb 18 22:35 UTC 1.8 27 Jul 17 18:50 UTC 1.7 18 Jul 17 19:23 UTC 1.6 16 Jun 17 22:41 UTC 1.5 06 Jun 17 23:28 UTC 1.4 29 May 16 05:03 UTC 1.3 08 Mar 16 19:10 UTC 1.2 23 Feb 16 02:18 UTC 1.1 26 Jan 16 18:13 UTC
デプロイの前後で自動テストなどの処理を入れたい場合は今のところ方法がありません。Tekton CDでは柔軟なデプロイメントパイプラインを構成できるので、今後Tekton CDのエコシステムが発達してきたら有力候補になるかもしれませんね。
本物のクラスタを利用してkubectl pluginをテストする
kubectlのプラグインを開発していると、ユニットテストだけでなく、本物のKubernetesクラスタを利用したテストが欲しくなります。プラグインの振る舞いが複雑な場合は自動テストがあると安心してリリースできます。
本稿では、本物のKubernetesクラスタを利用してkubectlプラグインをテストする方法を考えます。
テストの基本形
本物のクラスタを利用してプラグインをテストするための構成を下図に示します。
必要なのは以下の3つです。
テストの流れは以下のようになります。
- クラスタを作成する。
- 必要なリソースをデプロイする。
- kubectlを実行する。
- 間接的にkubectl pluginが実行される。
- 実行結果が期待通りか検証する。
このようなテストはプラグインのリリース前に手動でやっていることが多いと思います。自動テストを導入することで、
- リリース前ではなくPull Requestの契機で不具合を検出できる
- クリーンな環境でテストできるので信頼性が高い
- 手動では手間のかかる組合せテストが可能になる(複数のクラスタバージョンなど)
といったメリットがあります。
例: kubectl-treeのテストを書いてみる
ここでは、kubectl-treeというプラグインをテストする例を考えます。これはクラスタにあるリソースを木構造で表示してくれる便利なプラグインです。
簡単のため、テストシナリオはMakefileで書くことにします。Makefileをよく知らない方はシェルスクリプトだと思って読んでみてください。
クラスタの作成とリソースのデプロイ
まずは新しいクラスタを作成するターゲットを定義します。Kindを利用すると、Docker上に簡単にクラスタを作成できます。
CLUSTER_NAME := kubectl-tree-e2e-test OUTPUT_DIR := $(CURDIR)/output KUBECONFIG := $(OUTPUT_DIR)/kubeconfig.yaml export KUBECONFIG .PHONY: cluster cluster: $(OUTPUT_DIR)/kubeconfig.yaml $(OUTPUT_DIR)/kubeconfig.yaml: kind create cluster --name $(CLUSTER_NAME) kubectl cluster-info .PHONY: delete-cluster delete-cluster: kind delete cluster --name $(CLUSTER_NAME) -rm $(KUBECONFIG)
これでmakeを実行すると新しいクラスタが作成される仕組みができました。実行にはDockerとKindが必要です。
% make kind create cluster --name kubectl-tree-e2e-test Creating cluster "kubectl-tree-e2e-test" ... ✓ Ensuring node image (kindest/node:v1.17.0) 🖼 ✓ Preparing nodes 📦 ✓ Writing configuration 📜 ✓ Starting control-plane 🕹️ ✓ Installing CNI 🔌 ✓ Installing StorageClass 💾 Set kubectl context to "kind-kubectl-tree-e2e-test" You can now use your cluster with: kubectl cluster-info --context kind-kubectl-tree-e2e-test Not sure what to do next? 😅 Check out https://kind.sigs.k8s.io/docs/user/quick-start/ kubectl cluster-info Kubernetes master is running at https://127.0.0.1:32771 KubeDNS is running at https://127.0.0.1:32771/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'. % make delete-cluster kind delete cluster --name kubectl-tree-e2e-test Deleting cluster "kubectl-tree-e2e-test" ... rm /kubectl-tree-e2e-test/output/kubeconfig.yaml
テストを行うにはクラスタだけでなく適当なリソースも必要です。ここではechoserverをデプロイすることにします。以下のようにkubectl applyコマンドを追記します。
.PHONY: cluster cluster: $(OUTPUT_DIR)/kubeconfig.yaml $(OUTPUT_DIR)/kubeconfig.yaml: # create a cluster kind create cluster --name $(CLUSTER_NAME) kubectl cluster-info # deploy the echoserver kubectl apply -f fixture.yaml # wait for the deployment kubectl -n echoserver rollout status deployment/echoserver
これで、テストに必要なクラスタとリソースが揃いました。
プラグインの実行
kubectl treeコマンドを実行すると以下のような結果が表示されます。
% kubectl tree -n echoserver deployment echoserver NAMESPACE NAME READY REASON AGE echoserver Deployment/echoserver - 59s echoserver └─ReplicaSet/echoserver-5d8cc8d48 - 48s echoserver └─Pod/echoserver-5d8cc8d48-bcvs4 True 46s
ここでは簡単のため、grepで必要な文字列が表示されているかチェックします。もっと複雑な条件判定が必要な場合はスクリプトを書く方がよいでしょう。
.PHONY: test test: cluster # run kubectl-tree kubectl tree -n echoserver deployment echoserver | tee $(OUTPUT_DIR)/actual # make sure the output contains the expected lines egrep --color '^echoserver +Deployment/echoserver' $(OUTPUT_DIR)/actual egrep --color '^echoserver +└─ReplicaSet/echoserver-' $(OUTPUT_DIR)/actual egrep --color '^echoserver +└─Pod/echoserver-' $(OUTPUT_DIR)/actual
makeを実行してみましょう。以下のようにテストに合格するはずです。
% make # run kubectl-tree kubectl tree -n echoserver deployment echoserver | tee /kubectl-tree-e2e-test/output/actual NAMESPACE NAME READY REASON AGE echoserver Deployment/echoserver - 5m4s echoserver └─ReplicaSet/echoserver-5d8cc8d48 - 4m56s echoserver └─Pod/echoserver-5d8cc8d48-nk47g True 4m55s # check actual output egrep '^echoserver +Deployment/echoserver' output/actual echoserver Deployment/echoserver - 5m4s egrep '^echoserver +└─ReplicaSet/echoserver-' output/actual echoserver └─ReplicaSet/echoserver-5d8cc8d48 - 4m56s egrep '^echoserver +└─Pod/echoserver-' output/actual echoserver └─Pod/echoserver-5d8cc8d48-nk47g True 4m55s
これでローカルでのテストは完了です。
CIで実行する
品質を継続的に向上させるにはCIが不可欠です。ここではGitHub Actionsでテストを実行します。
GitHub ActionsのUbuntu 18.04にはすでにKindがインストールされていますが、バージョンが古いので改めて最新版をインストールすることにします。CIの最初のステップでは以下のツールをインストールします。
- Kind
- krew
- kubectl-tree
それからmakeを実行します。
最終的に、workflowは以下のようになります。
name: test on: [push] jobs: build: name: test # https://help.github.com/en/actions/automating-your-workflow-with-github-actions/software-installed-on-github-hosted-runners#ubuntu-1804-lts runs-on: ubuntu-18.04 steps: - uses: actions/checkout@v1 # https://kind.sigs.k8s.io/docs/user/quick-start/ - run: | wget -q -O ./kind "https://github.com/kubernetes-sigs/kind/releases/download/v0.7.0/kind-linux-amd64" chmod +x ./kind sudo mv ./kind /usr/local/bin/kind kind version # https://github.com/kubernetes-sigs/krew - run: | ( set -x; cd "$(mktemp -d)" && curl -fsSLO "https://github.com/kubernetes-sigs/krew/releases/download/v0.3.3/krew.{tar.gz,yaml}" && tar zxvf krew.tar.gz && KREW=./krew-"$(uname | tr '[:upper:]' '[:lower:]')_amd64" && "$KREW" install --manifest=krew.yaml --archive=krew.tar.gz && "$KREW" update ) # https://github.com/ahmetb/kubectl-tree - run: PATH=$PATH:$HOME/.krew/bin kubectl krew install tree - run: PATH=$PATH:$HOME/.krew/bin make
ここまでの内容は下記のリポジトリにまとめてあります。CIの実行時間や実行結果が気になる方はぜひご覧ください。
例:kubeloginの受け入れテスト
拙作のkubeloginでは、本物のKubernetesクラスタとOpenID Connect OPを利用したテストを下図の構成で行っています。
テストを支える裏方の仕組みは https://github.com/int128/kubelogin/tree/master/acceptance_test をご覧ください。
まとめ
DockerとKindを利用すると、本物のクラスタを用いたkubectl pluginのテストを実現できます。
決済手段を選択するビジネスルールを考える
お店やネットで買い物する時のビジネスルールが複雑になってきたので書き出してみました。
以下の順に評価して条件を満たす決済手段で支払います。
- USD/EUR建ての場合:Sony Bank WALLET
- ANA FESTAの場合:ソラチカカードVisa(5%割引)
- ビックカメラの場合:ビックカメラSuicaカード(10%)
- アトレやJR定期券の場合:JREカード(5%)
- 投資信託の場合:楽天カード(1%)→楽天証券
- コード決済が使える場合:Origami Pay(1%割引)→Kyash Visa(1%)→EPOS(1.5%)
- コード決済が使える場合:楽天Pay(1%)→Kyash Visa(1%)→EPOS(1.5%)
- Visa決済が使える場合:Kyash Visa(1%)→EPOS(1.5%)
- プリペイドVisaがダメな場合:EPOS (1.5%)
- 東京メトロ乗車の場合:ソラチカPASMO
- Suicaが使える場合:Suica → JREカードオートチャージ (3%)
- PayPayが使える場合:PayPay (0.5%)
- 現金
ただし、以下の例外条件があります。
- コード決済のキャンペーン期間中は還元率の高い決済手段を優先的に選択します。例えば、2/4時点では一部店舗でPayPay(40%)があります。
- Kyash Visaカードのポイント付与対象外(航空券や鉄道など)の場合はEPOSカード(or 楽天カード)で支払います。
- Kyash Visaカードの月間支払いが12万円を超えた場合はEPOSカード(or 楽天カード)で支払います。
- EPOSカードの年間支払いが100万円を超えた場合は楽天カードで支払います。
また、支払い前に以下を考慮します。
- 決済とは別にdポイントを付与できる場合があります。
- 西友ネットスーパーやApple Storeなどの場合はRebatesを経由してから発注します。
- ポイントサイトのキャンペーンがある場合があります。(最近あまり見ていない)
上記はFeliCa/NFC決済を考慮していません。FeliCa/NFC決済が使える場合はビジネスルールが変わってくると思います。
もし、幅広い決済手段に対応したプロキシがあったとしても、これらのビジネスルールを実装してテストするのは大変そうです。