CircleCIでSpinnakerのデプロイメントパイプラインをデプロイする
どうも、もうすぐ大学卒業の@timtimtim1026です。
もしコメントなどがございましたらDMなり、リプライしてください。
本記事はCircleCI Advent Calendarの14日目の記事になっています。
本記事
この記事では、CircleCIを使ってSpinnakerのデプロイメントパイプラインをデプロイする方法を解説します。
今回の目的としては、CircleCIを使いながら継続的デリバリー(以下、CD)を構築する一例を知ってもらうことです。また、そのなかで紹介するCircleCIの知識が何かしら役に立てられればいいなと思います。
考え方としては、今回使うCircleCIやSpinnakerに限らずあらゆるCDにも、CIにも言えることなのでSpinnakerだけに限った話ではありません。
実は、以前(2018年4月ごろ)にSpinnakerを用いて宣言的継続的デリバリーというものを提案しました。
継続的デリバリーのためのデプロイメントパイプラインも宣言的に、かつ、イミュータブルにしよう
というアイデアでした。
この記事はデプロイメントパイプラインをCircleCIを使ってデプロイするための記事なので注意してください。
例えばコードの更新からデプロイまで以下のような流れがあるフローだと本記事では赤枠の箇所に対応しています。
コンテンツ
本記事では以下のセクションで構成されています。
Spinnakerとは
Spinnakerとは
オープンソースな高速に安全に継続的デリバリーをするプラットフォーム
です。公式サイトは以下のサイトにあるのでご覧ください。
Spinnakerには大きな概念として、PipelineとPipeline Templateというものがあります。
Pipeline Templateという再利用可能なモジュールのようなものに、設定値を渡すとPipelineを生成することができます(もちろんいきなりPipelineを作ることもできます)。
Pipeline Templateによって生成されたPipelineはImmutableであり、宣言的なデプロイメントパイプラインを作ることができます。
さらにCircleCIを使うことによって自動化をすることができます。
GitOpsのような考えに近いのですが、設定ファイルをリポジトリに書いて、CIによって適宜Pipelineをデプロイします。
ブランチごとにfilterしたりできますが、宣言的に継続的にデプロイメントパイプラインを配置して、それに沿ってデプロイすることができるわけです。
デプロイメントパイプラインとは
少し深くデプロイメントパイプラインに注目します。
デプロイメントパイプラインとは要求された環境へ移行する一連の過程のことです。
例えば
v1.0
からv1.1
にDocker RegistryにあるアプリケーションのDocker Imageを更新- Docker RegistryからトリガーをうけたCDがデプロイメントパイプラインを実装する
- カナリアリリース
- Slack通知
- 待機
- 古いバージョンのアプリケーションへトラフィックを制限する - 再度、Slack通知
などデプロイといってもいろんな過程があります。
Spinnakerでは宣言的に書いて、容易にカナリアデプロイやBlue/Greenデプロイ、Red/Blackデプロイをすることができます。
継続的デリバリーの観点だと、デプロイまで携わってないチームメンバーがいるなら継続的デリバリーはできていないです。
すべての更新がすぐさまデリバリーする必要があります。
以下の画像は実際にSpinnakerで定義してみたパイプラインです。
このデプロイメントパイプラインでは
Configuration
: Google Cloud RegistryへのPushでトリガーDeploy Canary
でカナリアをデプロイ、Webhook
で「カナリアがデプロイされる」ことをSlackに通知- カナリアが「デプロイされたこと」をSlackに通知
- 古いサーバーを削除して、「カナリアデプロイが成功したこと」をSlackに通知
で構成されています。
仮にカナリアで何かしらのバグがあって、システムが落ちるようなことがあっても、トラフィックを切って元の安全なバージョンを使い続けることができ、逆にうまくデプロイできたら古いバージョンを削除して、安全にバージョンアップをすることができます。
またSpinnakerはPipeline Templateのテンプレート機能を用いるとPipeline自体がImmutableになるので安全で運用がしやすいです。
SpinnakerのCLIツール
CIからデプロイをするので今回はroerというSpinnaker公式CLIツールを使います。
実はCLIツールには二つ種類があって
- roer
- spin
があります。
もちろんGUIからでもデプロイメントパイプラインを定義することはできます。
このCLIツールを少し紹介してみます。
Roer
現在はメンテナンスモードで開発は行われていません。
安定性を求めるならば、このCLIツールを選定してもいいかもしれません。
サブコマンドもpipeline、pipeline-templateなど、やりたいことと機能が一致しており、使いやすい点もポイントかなと思います。
この記事ではroer
を使います。
Spin
次世代のCLIツールはspinというものがあるのですが、
This is under active development, and not yet intended for production use.
と書いてあり、まだプロダクション環境で使うのは控えた方がいいでしょう。
CLIツールといえどSpinnaker自体の機能はroerを使っても変わらないため、サポートの状況を見る必要があります。
しかしながら、こちらをフィードしておくといいかもしれません。
roerからデプロイする
ここからはValueを渡して、実際にSpinnakerのデプロイメントパイプラインを構築するための方法を解説します。
ローカル環境から試す
まず、最初にCIではなくてローカル環境からデプロイする方法を解説します。CIで実践する前にローカルで実行できると、あとからわかりやすくなると思います。
roerでは以下のようなYAMLファイルを書きます。
今回は.spinnaker/config.yml
に保存していますがファイル名と保存先は任意です。
あらかじめSpinnakerのインスタンスなりVMからポート転送してlocalhost:8084
でアクセスできるものとします。
schema: "1" pipeline: application: testappfromgui name: Atomic Canary Deployment template: source: spinnaker://atomicCanaryDeployWebhookTrigger variables: replicas: 5 loadbalancer: nginx-keke slackWebhookURL: https://hooks.slack.com/services/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
.pipeline.template.source
であらかじめ用意したatomicCanaryDeployWebhookTrigger
というPipeline Templateを用意します。
URLスキーマがspinnaker
なのでSpinnaker本体のストレージにあるPipeline Templateを参照しています。
深くはSpinnakerの機能なので解説しません。
また、ローカル環境(Spinnakerへポート転送しているとき)ではroerのpipeline save
コマンドを使って以下のようにデプロイすることができます。
env SPINNAKER_API=http://localhost:8084 go run cmd/roer/main.go pipeline save /go/src/github.com/KeisukeYamashita/advent-article/.spinnaker/config.yml
環境変数SPINNAKER_API
でリクエスト先を指定することになっていて、設定されていないとエラーになります。
どのようにCircleCIでやるのか
手順としては簡単です。ローカル環境でやらせているものをCircleCIにやらせたらいいのです。
またGithubのPushがトリガーになります。
手順をおって説明します。
1. SpinnakerのAPIサーバーにCircleCIからアクセスをする
まず、sshでCircleCIからGCPにアクセスをするのでサービスアカウントが必要になりますのでGCPのコンソールで作成してください。
サービスアカウント秘密鍵(json)を作成してクリップボードにコピーします。
cat /path/to/service/key/hoge.key | pbcopy
そしてCircleCIのSecretにGCLOUD_SERVICE_ACCOUNT
に保存します。
CircleCIではContextsで設定をすることができます。
詳しいドキュメントは以下のリンクで確認してください。
簡単にいうと環境変数などをKey-Valueで渡せるものです。
Contexts名はgcloud-context
と指定します。これがKeyとなります。
ここで追加でzone
情報も追加しました。
次にCircleCIのJobとして定義します。アンカーで定義しているので抜粋しました。
アンカー名はgcloud_auth
です。
gcloud_auth: &gcloud_auth run: name: Gcloud auth command: | echo ${GCLOUD_SERVICE_ACCOUNT} > ${HOME}/gcloud-service-key.json gcloud auth activate-service-account --key-file ${HOME}/gcloud-service-key.json gcloud config set project --quiet ${GCLOUD_PROJECT_ID} ssh-keygen -t rsa -C s02294 -f ${HOME}/.ssh/id_gcloud -N '' chmod 400 ${HOME}/.ssh/id_gcloud publickey=$(cat ${HOME}/.ssh/id_gcloud.pub) gcloud compute instances add-metadata keke-spinnaker --zone=asia-northeast1-a --metadata "^#&&#^ssh-keys=s02294:$publickey" ssh -i ${HOME}/.ssh/id_gcloud -o "StrictHostKeyChecking no" s02294@35.200.80.114 -L 9000:localhost:9000 -L 8084:localhost:8084 -fN
簡単に説明すると
echo
でファイルにサービスアカウントキーを書き出すgcloud
をアクティベートssh
で接続し、ポート転送をしている
サービスアカウントは最低限の権限だけ渡すようにしてください。
2. デプロイメントパイプラインをデプロイする
以下のようにデプロイします。アンカー部分を取り出したものです。deploy_pipeline
とアンカーを名付けました。
deploy_pipeline: &deploy_pipeline run: name: Deploy pipeline by roer command: | go get github.com/spinnaker/roer cd /go/src/github.com/spinnaker/roer env SPINNAKER_API=http://localhost:8084 go run cmd/roer/main.go pipeline save /go/src/github.com/KeisukeYamashita/advent-article/.spinnaker/config.yml
ここでは先ほどのローカル環境の時に説明したPipelineを作るコマンドpipeline save
をしています。
3. Jobを定義、Workflowにまとめる
CircleCIでは一つのタスクのまとまりとしてJobとして定義します。
Job
を以下のように定義しました。deploy_spinnaker_pipeline
が一つのJobです。
jobs: deploy_spinnaker_pipeline: <<: *docker_image_config_with_golang steps: - checkout - *gcloud_auth - *deploy_pipeline - *kill_gcloud_ssh
ここでは勝手にroerのテストを挟んでいます。Testはroerのtest
コマンドを叩いて実行しています。
以下のようにWorkflowにまとめます。
workflows: version: 2 build_and_test_and_deploy: jobs: - test - deploy_spinnaker_pipeline: context: gcloud-context requires: - test filters: branches: only: - master - develop
今のところ、簡単ですが流れとしてはこのようになっています。
ここではfilters
によってブランチでフィルタリングをかけています。
またrequires
によって並列ジョブと逐次ジョブを指定することができ、スケジューリングすることができます。順序性がなければ並列化してワークフローを速く終えることができます。
また、build_and_test_and_deploy
はtest
が成功したときに走るように設定しています。
4. Container RegistryにPushしたり、、、
Docker環境で運用していると、例えばGCPなんかはContainer RegistryにPushすることを追加してもいいかもしれません。
SpinnakerのProvider(トリガーをもらう先)にContainer Registryを設定することもできるので、トリガーを発せられるようになります。
- send_webhook_to_slack: context: gcloud-context requires: - deploy_spinnaker_pipeline filters: branches: only: - master - develop - build_image_and_push_gcr: context: gcloud-context requires: - deploy_spinnaker_pipeline filters: branches: only: - master - develop
またSlackに通知を送るwebhookも指定します。 SlackのIncoming Webhookを使っていて、URLをさらにContextで渡しています。
用途としては、ワークフローの途中経過をフィードバックしたいからです。
5. 最終的なWorkflow
GithubにデプロイメントパイプラインのリポジトリをPushすると以下のようにWorkflowがはしります。
またbuild_image_and_push_gcr
によってデプロイしたパイプラインでのトリガーとなって、Spinnakerによってデプロイされるようになっています。
最終的な流れとしては以下のようになっています。
そのあとの話
Mergeによるデプロイメントパイプラインの更新
デプロイメントパイプラインはブランチごとなど、必要に応じて定義できて、実行できます。
例えば今回だと.spinaker/config.yml
のように設定ファイルでしかないので、ブランチ間の差分を読むことによってデプロイメントパイプラインの差分がわかります。
つまりconfig.yml
ファイルがデプロイメントパイプラインを宣言的にすべてを記述しているのでGitOpsということができます。
config
を読めばデプロイメントパイプラインがわかるconfig
を書けばデプロイメントパイプラインを宣言できる
その仕組みを今回、CircleCIにやってもらったわけです。
アプリケーションが運ばれる流れ
デプロイメントパイプラインとアプリケーションの更新が同時である必要はないですが、今回は説明のために同時に説明します。
例えばエンジニアがアプリケーションに対して差分をPushしたとします。
この差分には
- デプロイメントパイプラインの更新
- アプリケーションの更新
があったとします。
次にその差分がMasterブランチにマージされてCIがトリガーされたとします。
ここからが醍醐味なのですが、まずデプロイメントパイプラインの更新をします。
差分が追加されました。
これによってデプロイメントパイプラインは最新の差分を含んでいることになります。
次にGCRに新しいアプリケーションのイメージをPushします。
すると今回だとGCRの更新によってSpinnakerがトリガーされるので、トリガーされてアプリケーションはデプロイメントパイプラインを流れていきます。
するとあとは自らが定義したデプロイメントパイプラインの出力先(例えばKubernetesクラスタ)などにデプロイされるだけです。
この一連の流れをCircleCIとCDツールをもってして実現することができます。
CI/CDの恩恵
例えばインフラもサーバーのこともよくわからない新米デザイナーがいたとしましょう。
HTMLやCSSを軽くいじってPushして、チームリーダーにマージしてもらいました。
CI/CDを敷いているおかげで、何もインフラなどは知らないのにデプロイをする(=開発した成果物を届ける)ことができます。
もちろんSlack通知などの通知によるフィードバックはもちろんのことながら
Spinnaker自体のGUIでもフィードバックを得られます。
ただ知らないというだけでなくて、安全に、チームだれもが、フィードバックを得ながらデプロイしていく、価値をユーザーに届けられるプラットフォームが実現したというわけです。
まとめ
今回はSpinnakerでやりましたが、CDのデプロイメントパイプライン自体をデプロイするためにCIを使う方法を紹介しました。
明日はKubernetes Advent CalendarにArgo CDやGitOpsについて記事をアップします。CDについて興味が出てきた人はぜひ見てみてください。
ありがとうございました。