☸️

GitOps を実現する CD ツール、PipeCD が良さそうという話

2020/12/11に公開

この記事は Qiita: Kubernetes Advent Calendar 2020 の 11 日目の記事です。

当初は PipeCD Operator を実装してみたことを記事にしようと考えてたのですが、その前に PipeCD 自体の話をするべきだと思い当記事を書きました (なので Kubernetes 成分は少なめです)。
PipeCD Operator の実装については別途記事を書く予定です。
書きました

導入

最近話題になった CD ツールである PipeCD について軽く検証してみたところ良いなと感じたため記事を書きます。


まず自分がどの程度事前知識を持った上で PipeCD の検証をしたか、参考程度に晒しておきます。

  • Spinnaker をお仕事で利用している
    • Provider は Kubernetes V2
    • リリースは Git Tag 運用
    • デプロイ戦略は Argo Rollouts (without Istio) を用いた B/G Deployment
  • ArgoCD を趣味の自宅 K8s クラスタで利用している

PipeCD

PipeCD はマルチクラウド向けに Continuous Delivery / Progressive Delivery を実現するソフトウェアです。また、デプロイ対象リポジトリの更新契機で PipeCD の Application (デプロイパイプライン) が動作する、つまり GitOps を実現する CD ツールとなっています。

v0.9.0 現在、PipeCD は以下のプロバイダに対応しています。

  • Kubernetes
  • Terraform
  • Cloud Run
  • Lambda

PipeCD の特徴

PipeCD の一番の特徴は、 Progressive Delivery を司る機能の配置先がロックインしないことだそうです[1]
同じく Progressive Delivery を行うソフトウェアである Flagger や Argo Rollouts は、 Kubernetes の Operator として動作することでステートは Custom Resource として Kubernetes のデータベース内 (etcd) にデータを保存する必要があるため、Kubernetes にロックインされます。
PipeCD はコンポーネントが大きく ControlPlane (Application のステート管理や API/WebUI 提供などのコンポーネント群) と Piped (実際に Delivery を制御するコンポーネント) に分かれており、Piped から ControlPlane へ定期的に問い合わせることで動作しています。(Kubernetes の Reconciliation Loop に似た動きに見えます!)
これにより PipeCD の利用者は、 ControlPlane へ疎通が取れるところであればどこにでも、Piped というステートレスなワンバイナリの agent を動作させるだけで様々なプロバイダに対する Application の Delivery が可能となります。

pipecd_overview[2]

また、 PipeCD はマルチクラウド向けの CD ツールであるという特徴もあります。Piped コンポーネントにてデプロイ先に応じた設定をすることで同じシステムから様々なプロバイダへのデプロイが可能になります。

マルチクラウド向けな CD ツールは他にも Spinnaker 等がありますが、それらの既存ツールとの大きな違いは PipeCD が GitOps を前提としたツールであることです。
Spinnaker でも GitOps を出来ないことはありませんが、そのためには Spinnaker の多くの機能を取捨選択し適切に設定する必要があるため手間がかかります。その分シンプルに GitOps を前提として動作する PipeCD は簡単にセットアップできるという利点があります。

検証

ここからは実際に PipeCD を触ってみた検証結果を書き下します。
マルチプロバイダ向けの CD ツールですが、今回は Quick Start に沿って Kubernetes へのデプロイを検証しました。

検証時のバージョンは以下になります。

  • PipeCD v0.9.0
  • Kubernetes v1.19.1 (kind)

事前準備: マニフェストファイルのダウンロード

Quick Start 用のマニフェストファイル (Helm Chart) が配置されたリポジトリが公開されているため、事前にクローンします。

git clone https://github.com/pipe-cd/manifests.git
cd manifests
git switch -d v0.9.0

以降の CLI の操作は当ディレクトリ以下で実施します。

マニフェストの適用①: ControlPlane のインストール

以下のコマンドで ControlPlane をデプロイします。

helm -n pipecd install pipecd ./manifests/pipecd --create-namespace \
  --values ./quickstart/control-plane-values.yaml

このとき、以下のコンポーネントがデプロイされます。

Component 役割
Server html, css, js の serve、ブラウザ (js) からの gRPC Web な通信を Gateway コンポーネント経由で受信、Piped からの gRPC な通信を受信
Gateway gRPC Web をプロキシするための Envoy
Cache Server のキャッシュ、実態は Redis
Ops 管理者用ページを配信するコンポーネント、現状 Project の List と Add ができる
Data Store PipeCD の Application (デプロイパイプライン) の保存先。GCP Firestore, AWS DynameDB, MongoDB が利用可能 (Quick Start では MongoDB を K8s で動作させて利用)
File Store PipeCD のステージログ (デプロイパイプラインの各ステップの実行ログ) や Application live state の保存先。GCP GCS, AWS S3, Minio が利用可能 (Quick Start では Minio を K8s で動作させて利用)

control-plane-components[3]

このとき、 ServerOps には以下のファイルが --config-file オプションで渡されています。
設定ファイルのフォーマットは Kubernetes マニフェストそのものですが、 PipeCD の特徴で述べたとおり PipeCD は動作環境が Kubernetes 以外の場合も想定しているため、設定を (Kubernetes の Custom Resource ではなく) バイナリ実行時の引数で渡すという形を取っています。

apiVersion: "pipecd.dev/v1beta1"
kind: ControlPlane
spec:
  datastore:
    type: MONGODB
    config:
      url: mongodb://pipecd-mongodb:27017/quickstart
      database: quickstart
  filestore:
    type: MINIO
    config:
      endpoint: http://pipecd-minio:9000
      bucket: quickstart
      accessKeyFile: /etc/pipecd-secret/minio-access-key
      secretKeyFile: /etc/pipecd-secret/minio-secret-key
      autoCreateBucket: true
  projects:
    - id: quickstart
      staticAdmin:
        username: hello-pipecd
        passwordHash: "$2a$10$ye96mUqUqTnjUqgwQJbJzel/LJibRhUnmzyypACkvrTSnQpVFZ7qK" # bcrypt value of "hello-pipecd"

このタイミングで上記設定ファイルの .spec.projects 以下の内容より、quickstart Project が作成されます。

WebUI の操作: Environment・Piped 設定の作成

先程の手順で ControlPlane の各コンポーネントがデプロイされたため、これ以降はポートフォワーディングを実行することで WebUI へアクセスことができます。

WebUI にて以下を設定します。

設定項目 役割
Environment Application の論理的なグループ
Piped Piped コンポーネントをデプロイするための ID と Secret を発行する

今回は example-environment Environment と example-piped Piped を作成しました。

これらの設定と、 1 つ前の手順で出てきた Project 、この後の設定にて出てくる Application はそれぞれ以下の関係性です。

er

マニフェストの適用②: Piped のインストール

以下のコマンドで Piped をデプロイします。 $PIPED_SECRET には前回の手順で発行した Piped Secret を入力します。

helm -n pipecd install piped ./manifests/piped \
  --values ./quickstart/piped-values.yaml \
  --set secret.pipedKey.data=$PIPED_SECRET

Piped も ControlPlane と同様に以下の Kubernetes マニフェストのフォーマットの設定ファイルが実行時引数で渡されます。 .spec.pipedID には 1 つ前の手順で発行した Piped ID を入力します。

apiVersion: pipecd.dev/v1beta1
kind: Piped
spec:
  projectID: quickstart
  pipedID: $PIPED_ID
  pipedKeyFile: /etc/piped-secret/piped-key
  apiAddress: pipecd:8080
  webAddress: http://pipecd:8080
  syncInterval: 1m
  repositories:
    - repoId: examples
      remote: https://github.com/ShotaKitazawa/examples.git
      branch: master

このタイミングで上記設定ファイルの .spec.repositories 以下の内容より、Piped が Watch する Git リポジトリを指定します。Private リポジトリの場合は ssh 用の秘密鍵ファイルの指定も当箇所で設定可能です。[4]

WebUI の操作: サンプルアプリ (Canary Release) Application の作成

WebUI で Application を作成します。Application には以下の役割があります。

設定項目 役割
Application 特定の Piped より実際にプロバイダ (今回の場合 Kubernetes) へ Delivery を行うための設定

今回は example-application-canary という名前で Application を作成します。

application

この Application が指定している箇所は https://github.com/ShotaKitazawa/examples/tree/master/kubernetes/canary になります。

当ディレクトリを見ると Kubernetes マニフェストファイルの他に .pipe.yaml というファイルが在ることを確認できます。この .pipe.yaml によって何のプロバイダ向けのファイルを管理しているディレクトリであるかの情報や、デプロイ Pipeline を制御可能です。

.pipe.yaml ファイルも Kubernetes マニフェストのフォーマットで設定を記述します。

# Deploy progressively with canary stragegy.
apiVersion: pipecd.dev/v1beta1
kind: KubernetesApp
spec:
  pipeline:
    stages:
      # Deploy the workloads of CANARY variant. In this case, the number of
      # workload replicas of CANARY variant is 10% of the replicas number of PRIMARY variant.
      - name: K8S_CANARY_ROLLOUT
        with:
          replicas: 60%
      # Wait 10 seconds before going to the next stage.
      - name: WAIT
        with:
          duration: 10s
      # Update the workload of PRIMARY variant to the new version.
      - name: K8S_PRIMARY_ROLLOUT
      # Destroy all workloads of CANARY variant.
      - name: K8S_CANARY_CLEAN

さて、上記の .pipe.yaml の配置先を指した Application を作成すると、実際に Kubernetes へマニフェストが適用されます。
初回デプロイ時は .pipe.yaml ファイルに書いた Pipeline の内容によらず K8S_SYNC stage のみが走り以下の状態になります。

canary_0

では Application の管理しているマニフェストファイルを更新してみます。今回は Image Tag を v0.1.0 から v0.2.0 に更新します。

deployment.yaml
-         image: gcr.io/pipecd/helloworld:v0.1.0
+         image: gcr.io/pipecd/helloworld:v0.2.0
git add .
git commit -m "update to v0.2.0"
git push origin HEAD

すると数秒後に Pipeline が走ったことを WebUI より確認できます。

このとき、それぞれのステージの内部動作は以下のようになっています。

  1. K8S_CANARY_ROLLOUT stage: {.metadata.name}-canary Deployment として v0.2.0 をデプロイ (付与される label は図の通り)
    • replicas: 60% を与えているため、 canary (新バージョン) は primary (旧バージョン) の 60% の Replica 数でデプロイ
      • 例では primary の Replicas が 3 なので、 canary の Replicas は 2

canary_1

  1. K8S_PRIMARY_ROLLOUT stage: primary な Deployment を v0.2.0 に RollingUpdate

canary_2

  1. K8S_CANARY_CLEAN stage: canary な Deployment を削除

canary_3

以上の動作により PipeCD は Canary Release を実現します。

なお未検証ですが、Istio を導入することで Piped が Service の代わりに VirtualService を操作することで細かいトラフィックコントロールをすることも可能みたいです。[5]

感想をつらつらと

PipeCD を触ってみました。

PipeCD の Progressive Delivery を司る機能の配置先がロックインしないという特徴はプロバイダが Kubernetes 以外の場合においては勿論利点ですが、プロバイダが Kubernetes である場合においてもツール自体のバージョンアップ作業をする上で大きな利点と成り得ると自分は思っています。
Progressive Delivery を行える競合のソフトウェアとして Argo Rollouts がありますが、このソフトウェアは Argo Rollouts の更新による CRD (スキーマ) の変更や Kubernetes 自体の更新による CRD の仕様変更などが起こり得るため、バージョンアップ作業前に既存の Rollout リソースにどの程度影響があるか調査し慎重に更新する必要があります。
対して PipeCD は上記検証結果の通り Kubernetes の CRD を一切使わずに Progressive Delivery 出来ます。あくまで PipeCD (Piped) は Kubernetes の標準リソースを操作するのみを行うため、PipeCD の更新作業は Argo Rollouts の更新作業よりも小さい影響範囲であることが大きな利点であると思っています。

また他にも個人的に良いなと感じた点として、 GitOps を実現するツールなのに Pipeline を組めることが挙げられます。Argo CD や Flux CD は Git リポジトリにあるマニフェストを基本的に適用するだけなので、それに対して PipeCD は Pipeline でより細かい制御を出来ることが嬉しいです。
ただ Pipeline * GitOps の場合、Pipeline が途中で失敗した場合や途中でキャンセルした場合において GitOps の信条である Single Source of Truth を満たすことが出来ません。この解決策の一例として、 Pipeline の 1 stage として「Git リモートリポジトリ (例. GitHub) 上のブランチ A からブランチ B への merge を行う」という機能を作ることが挙げられます。これにより、K8S_PRIMARY_ROLLOUT ステージの後にこのステージを置くことで SSoT なブランチを作成出来ます。[6]
これについては https://github.com/pipe-cd/pipe/issues/1089 にてカスタムステージが欲しいという issue をあげたため、もしこの機能が実装されれば上記で述べたやりたいことも出来るようになります!

それと現状まだ TBA ですが、 Insights の機能にも注目しています。
Change Failure RateLead Time for Changes のメトリクスはメルカリさんが Spinnaker の SRE 運用の際の SLI として取り入れている Pipeline execution success ratePipeline execution duration [7] に合致するため、この辺りのメトリクスが簡単に取れると PipeCD を共通のデプロイ基盤として運用する際に便利そうだと感じました。(Spinnaker だとこの辺りのメトリクスが簡単には取れないので尚更楽しみです)

また個人的に大きな利点として、日本発の OSS であるということも挙げられます。自分は英語が得意ではないので、英語で書かれた GitHub や Slack のやりとりは頑張って読むのですが、いざというときに Twitter 等にて日本語で開発者の方とやり取りできるのはとてもありがたいです。

PipeCD はこれから安定版 (v1.x) を目指すフェーズだそうなので、今後もアップデートを追いながら、自分も少しでも PipeCD に貢献できればいいなと思っています。

脚注
  1. https://cloud-native.slack.com/archives/C01B27F9T0X/p1602479052013800 (https://cloud-native.slack.com の #pipecd) ↩︎

  2. https://pipecd.dev/docs/overview/ ↩︎

  3. https://pipecd.dev/docs/operator-manual/control-plane/architecture-overview/ ↩︎

  4. https://pipecd.dev/docs/operator-manual/piped/installation/ ↩︎

  5. https://github.com/pipe-cd/examples/blob/master/kubernetes/mesh-istio-canary/.pipe.yaml#L11-L17 ↩︎

  6. 参考: https://cloud.google.com/kubernetes-engine/docs/tutorials/gitops-cloud-build ↩︎

  7. https://speakerdeck.com/tcnksm/sre-practices-in-mercari-microservices?slide=16 ↩︎

GitHubで編集を提案

Discussion