The Eight-Factors CICD - Best Practices


## tl; dr

アプリケーションの世界では [The Twelve-Factor App](https://12factor.net/ja/) にアプリケーションが守るべき 12の要素がベストプラクティスとして定義されていますが、`Continuous Integration` や `Continuous Deployment` の世界ではあまり目にしたことがなかったので、自分なりのCICDのプラクティスまとめてみました。

## Who Am I

日頃、 k8sの環境設計・構築・運用や、CICDを中心とした DevOps Platform、監視、セキュリティ、API Management などの運用面の支援など、Platformに関する全般的な支援をさせていただいています。
普段、CICD ツールは オンプレなら Jenkins または Screwdriver、 クラウドなら Build Kite とかを使ってます。

## CICDの目的

ここでは CICD の目的を、 **開発者が開発に専念できるようにすること** としています。開発に専念できるとは、 Build, Test, Deployment, Release, Tagの発行など、開発したものをエンドユーザーに提供するために必要なオペレーションは全てCICDの仕組みに内包することで、開発の品質と速度の向上を実現できることを指しています。

## Eight Factors CICD

上記の [CICDの目的](#CICDの目的)を達成するために、CICD があるべき 8つの状態、ということで Eight Factors CICD を定義しています。CICD や Devops の画像を見るといつも ∞ の形をしているため、8つということにしました。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252218/93e48930-ce9c-8915-2fb6-985bcc65f294.png" width="200" />

では、8つの要素についてそれぞれ言及していきます。

#### Scalability

アプリチームが求めるジョブの量に対して迅速に拡張できることを指しています。例えば、月間1,000ビルド実行することが求められるなら、それに迅速に拡張できることが求められます。Google Cloud Build や AWS Code Build などの Cloud 系を利用するなら、この点は意識しなくて済みますが、Jenkins などのオンプレミス CICD を使う場合、 Jenkins Slave の Scalability を維持するために工夫を行う必要があります。例えば、 Jenkins の裏側で Code Build や ECS (Fargate) を使ったりすることで解決していることが多いです。

一方で、「Scale するためには新しくVMやAgentを追加しなければならない」というようになっている場合は、CICDの `Scalability` は低い状態と考えています。

#### Simplicity

CICD 開発運用者だけでなく、アプリ開発者にとっても可読性が高く、コンフィグがしやすいことを指しています。例えば、Jenkins はカスタマイズが容易でその柔軟性はツール群の中でも飛び抜けていますが、その反面、複数の Plugin を組み合わせたり、groovy script による作り込みが必要であったりと、専門のCICD運用者がいなければ CICD を継続的に改善したり、アプリチームの要望に迅速に応えたりすることは難しくなります (Jenkins おじさん現象)。

特に、CIの部分は開発チームや開発言語などによって様々であり、開発のステージによっても柔軟に Pipeline を変えたい、という要望があります（例えば結合テストするタイミングではないためスキップしたい等）。その都度CICD運用者に問い合わせが必要な状態は望ましくありません。極端に言えば多少の変更ならパイプラインをアプリユーザーでも変更できるようなシンプルさがあることが望ましいです。

#### Dev Friendly

アプリ開発者が特定の知識や経験がなくてもアプリをデリバリーできることを指しています。アプリチームへの教育を通じて CICD を使いこなし度を高めることも可能ですが、アプリチームに特別な知識を求めると当初の目的である 「開発者が開発に専念できること」という目的から逸れることになりかねません。

Git の Commit / Push / PR / Merge などの開発の操作からシームレスに CICD の基本的な操作が実行できる必要があります。また、Rollback などが必要な場合は CICD の GUI 上から簡単に操作できるようなウェブコンソールを提供できることが望ましいです。

#### Configuration as a Code

ツールの設定や Job の情報をコードとして落とせているかどうかを指しています。 [Jenkins Configuration as Code](https://jenkins.io/projects/jcasc/) や [Managing infrastructure as code with Terraform, Cloud Build, and GitOps](https://cloud.google.com/solutions/managing-infrastructure-as-code) などで説明されているように、マニュアル作業や手順書ではなく、Platformのあらゆる情報をコード化し、透明性や再現性、再利用性や変更容易性を担保する必要があります。クラウド系の CICD では多少限界がありますが、Terraform や Ansible 駆使して、極力冪等性が担保されたスクリプトを用意されることが望まれます

#### Pipeline as a Code

Pipeline の情報をコードに落とせているかどうかという点を指しています。 Jenkinsなら `Jenkinsfile`、 Droneciなら `.drone.yml` になります。一方で、Jenkins のジョブを GUI で手作業で編集しているような場合は Pipeline as a Code を満たせていないと考えます。

ただし、より重要なのは、Pipeleine のコードをいかにうまく管理できているかということになると思います。 Pipeline をコード化した際に、各アプリのレポジトリに何百行もある Jenkinsfile が格納されている場合、Pipelineに変更を加えることが難しく管理が煩雑になるため Pipeleine as a Code の成熟度は低いと考えます。 

上記の解決策になるのが、Pipeline を関数化 (テンプレート化) です。 CircleCI なら [Orbs](https://circleci.com/orbs/) 、Jenkins なら [Shared Library](https://jenkins.io/doc/book/pipeline/shared-libraries/) 、 Screwdriverなら [Templates](https://docs.screwdriver.cd/user-guide/templates) などを駆使して関数化していくことで、極力アプリのレポジトリがCICDと疎結合になるように作っていくことが重要となります。

コマンド単位の小さな部品 (gradle や maven など、言語やパッケージ管理ツールごとに作るレベル。この部分は 既存の Orbs や Plugin で対応できることも多い。) を組み合わせながら、Build / Test / Deploy といったステージ単位の部品を作り、それらを組み合わせて API 開発者向けの Pipeline 、 Mobile 開発者向けの Pipeline などの Pipeline Templateを作成し、それをアプリユーザーが簡単に選択できるようにする、とできているのが理想的です

#### Performance

ビルド・デプロイジョブのスピードを指しています。当たり前ですが、ジョブがトリガーされたタイミングから、ジョブが完了するまで早ければ早いほど望ましいです。個人的な感覚ですが、１回の Job が 10分を超えてくると Job を実行することにストレスを感じ始める気がします。Performanceを上げるためには、例えばビルド時なら 依存関係の解決するのが早い package manager を使う (e.g. `npm` より `yarn`  とか、ライブラリ系のインストールは毎回しなくていいようにするとか、社内の `Nexus` や `Artifactory` とかを使うことでローカルネットワークや社内回線を使って回線速度をあげるとかがあります。

#### Loose Coupling

ビルド ~ デプロイが疎結合になっているかどうか、つまり別々に独立して実行もできるし、一連の流れを同期的に実行することもできる、ということを指しています。

ビルド ~ デプロイを別々に実行できないということは、毎回のデプロイにアーティファクトのビルドが必要ということになり、アプリ開発者の時間を消費してしまうことになります。

例えば、k8s の環境で、SpringBootの Pod に `application.yaml` などの設定ファイルを ConfigMap で外部から mount している場合など、 設定ファイルにのみ変更が発生しており、 jar / war / container image までのビルドは不要の場合は、設定ファイルのみを書き換えて迅速にデプロイできるとよりアプリチームが開発に専念することが可能となります。

この点を実現するためには、アプリの設定ファイルはアプリのレポジトリとは別のレポジトリ、または Configuration Management Server を用意するなどの工夫が必要となります。アプリのソースコードと設定ファイルを同梱すると、設定ファイルとソースコードのコミットの区別ができず、CICDのトリガーが難しくなってしまうためです。

余談ですが、Configuration Management は、例えば [ConfigHub](https://www.confighub.com/) という Open Source のものがあります。

#### Functional Coverage

機能的に CICD の求められる要素をカバーできているかを指しています。ツールとしては標準機能としてカバーできる範囲が広いほど、もりもりコンフィグする量が減るということなります

CICD を 全社的な開発プラットフォームとして捉えた場合、特定の環境や開発にのみ対応している、という状態はあまり望ましくはありません。例えば、Tekton は k8s native で、 k8s に関しては非常に使いやすく個人的に好きなのですが、Mobileのビルドやリリースに向いているかと言われるとそれは違うかと思っています。

なお、私がこれまで遭遇した CICD にアプリチームから求められる代表的な機能的要素は下記のようなものです（思い出せる範囲ですが）

1.  ビルド / アーティファクト管理
    - アプリのビルド (Spring BootなどのAPI系や Mobileアプリなど色々)
    - Container Image のビルド
    - VM イメージのビルド
    - Android OS のビルド
    - ビルドされたアーティファクトの管理
2.  テスト / 品質管理
    - 単体テスト
    - Coverage 計算
    - テスト結果レポーティング
    - コードの品質チェック
    - ライブラリやDocker Imageなどの脆弱性チェック
    - 結合テスト
    - シナリオテスト
    - 画面テスト
    - 回帰テスト
    - バグ注入テスト
3. デプロイ
    - Blue Green Deployment
    - Imutable Deployment
    - Promotion (ビルドは Dev環境での一回のみで、アーティファクト を次の環境へ昇格させる)
    - Rollback
    - Re-Deploy
4. リリース
    - Canary Release
    - A/B Testing
    - Traffic Configuration
5. その他
    - 複数環境のバージョンの同期 (k8s をマルチクラスターで運用するためにCICDで同期を図る場合など)
    - 深刻度に応じた通知先や通知方法の動的な変更
    - 実機のアクチュエーション (IoT など物理機器が存在する開発の場合)

## さいごに。

一旦、Eight Factors CICDと題して書きなぐってみましたが、まだまだ完成度も低いですし、他にもこういう視点がかけているとか、色々ご指摘があればぜひコメントいただければ嬉しいです。余談ですが、[CD Foundation](https://cd.foundation/projects/) にノミネートされている中では、 [Screwdriver.cd](https://screwdriver.cd/) が好きです。