CircleCIまとめ

たまにさわることがあるが、基礎的な概念を思い出すのに毎回けっこうな時間がかかるため、自分用にまとめた。
(CircleCI の version は 2.1 前提)

# CircleCI とは
CI/CD パイプラインを実現する CI/CD ツールの一つ。

**CI (continuous integration 継続的インテグレーション)**
コードの変更を自動的にテストするプロセス。

**CD (continuous delivery 継続的デリバリー)**
CIのテストを通過したコードを自動的にデプロイするプロセス。

CircleCI では、Git ホスティングサービス(GitHub 等)のリポジトリへの push をトリガーに、設定された工程にしたがってリポジトリのcloneしプロジェクトのビルド、テスト、デプロイを実行する。

# CircleCI での CI/CD パイプラインの設定
**簡単なイメージ図**
![circleci.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59542/c889360e-5105-2944-e243-870f9aea9adb.png)

## pipeline
`pipeline` は複数の`workflow`で構成されるものである。
`pipeline` の設定はプロジェクトルート直下の``.circleci/config.yml``に記述する。
`pipeline` は CircleCI の API によって実行される。
API のトリガーパターンは以下の通り。
- CircleCIと連携するGitホスティングサービス上のリポジトリのブランチにPUSHされたとき(最も基本的なトリガー)
- スケジュールした日時に達したとき
- CircleCI の Web アプリの UI で手動実行したとき
- 直接 CircleCI の API を実行したとき

## workflow
`workflow` は複数の `job` の並びで構成されるものである。
`workflow` に含まれる各 `job` の実行トリガーは、並列実行/順次実行/手動承認から設定可能である。
CircleCI は、API がトリガーされると全ての `workflow` を並列に実行しようとする。

## job
`job` は複数の`step`の並びで構成されるものである。

## step
具体的な処理1つ1つ。

# .circleci/config.yml
## 概要
CircleCIのpilelineは``.circleci/config.yml``で設定し、主に以下のようなブロックで構成される。

![circleci-config.ymlの簡単な構成図.drawio (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59542/ac1015c5-fb03-6522-606d-74f007543dca.png)

具体的な記述の簡単なサンプルは以下のような形になる。

```.circleci/config.yml
# CircleCI のバージョン
version: 2.1

# ジョブ群を列挙
jobs: 
  # job1の設定
  job1: 
    # 実行環境の設定(下記はDockerコンテナで実行する場合を直接記述したもの。)
    docker:
      # ベースイメージを指定
      - image: <ベースイメージ>
    # job1内で実行する一連のstepを記述
    steps:
      - <step1>
      - <step2>
      ...
  # job2の設定
  job2: 
  ...

# workflow群を列挙 
workflows: 
  # workflow1の設定
  workflow1:
    # workflow1内で実行するジョブのリストと実行トリガーを列挙
    jobs:
      - job1: 
          <job1の実行条件> 
      - job2:
          <job2の実行条件> 
  workflow2:
  ...  
```



**参考**

https://circleci.com/docs/ja/concepts/#pipelines

https://circleci.com/docs/ja/glossary/

## job の実行トリガー設定
### 並列実行
実行トリガーに何も設定しない場合、それらのジョブは同時実行される
```config.yml
workflows: 
  sample_workflow:
    # job1とjob2を同時実行
    jobs:
      - job1:   
      - job2:
```

### 順次実行
```
- <ジョブ2名>
    requires: 
      - <ジョブ1名>
```
と設定すると、ジョブ2の実行トリガーを「ジョブ1の正常完了後」とできる。
これを応用して、以下のようにファンイン/ファンアウトも実現できる。
```config.yml
workflows: 
  sample_workflow:
    # job1実行後にjob2, job3, job4を同時実行し、すべて完了したらjob5を実行する
    jobs:
      - job1: 
      - job2:
          requires:
            - job1
      - job3:
          requires:
            - job1
      - job4:
          requires:
            - job1
      - job5: 
          requires:
            - job2
            - job3
            - job4
```

### 手動承認での実行
```
- <ジョブ名>
    type: approval 
```
とすると、CircleCI のワークフローのページで「Approval」ボタンを押下するまでジョブの実行を保留できる。
以下のように requires とも併用可能。

```config.yml
workflows: 
  sample_workflow:
    # job1実行後の手動承認後にjob2を実行
    jobs:
      - job1:
      - job2:
          type: approval
          requires:
            - job1
```

**参考**

https://circleci.com/docs/ja/workflows/

---

## executors
jobの実行環境の事前定義である。

```
executors:
  <executor名>:
    <実行環境の設定>
```


定義した executor は以下の箇所で参照できる。
```
jobs:
  <job名>:
    executor: <executor名>
```


例えば以下のように記述し、この場合 sample-job の step は、Docker イメージ cimg/base を元に起動されるコンテナ上で実行される。
```
executors:  
  sample-executor:
    docker:      
      - image: cimg/base
...


jobs:
  sample-job:
    executor: sample-executor
    steps:
      ...
```

参考: [Executor]()


## commands
一連のstepの事前定義である。

```
commands:
  <command名>:
    <一連のstep>
```

定義したcommandは以下の箇所で参照できる。

```
jobs:
  <job名>:
    executor: <executor名>
    steps: 
      - <command名>
```

## parameters
型とデフォルト値含めて定義可能な引数である。デフォルト値を定義できることから、定数を定義する用途でも使える。
parameters は、特定の箇所でしか定義できない。
定義の記述は、以下のように行う。

```
paramters:
  <parameter名>:
    type: <型>
    default: <デフォルト値>
```

parameter の値を渡せる箇所も限られている。
parameter を定義できる箇所、値を渡せる箇所を一覧化すると以下の通り。

定義できる箇所|値を渡せる箇所
---|---
pipeline(``.circleci/config.yml``内の第一階層)|なし。CircleCIのAPI実行時に渡す。具体的にはCircleCIのUI上や直接APIを叩くときに渡すことができる。ブランチへのPUSH時は渡せず、確定でデフォルト値が適用されることになる(模様)。
``executors.<executor名>``|``jobs.<job名>.executor``
``commands.<command名>``|``jobs.<job名>.steps.<command名>``
``jobs.<job名>``|``workflows.<workflow名>.<job名>``


pipeline レベルの parameter は、``.circleci/config.yml``内ならどこでも``<< pipeline_parameters.<parameter名> >>``で参照できる。
executor, command, job レベルの parameter は、それぞれの定義内限定で``<< parameters.<parameter名> >>``で参照できる。

なお、executor を parameter 値を渡しつつ呼び出す場合、以下のような記述になる。

```
jobs:
  <job名>:
    executor: 
      name: <executor名>
      <parameter名>: <parameter値>
```

# その他詳細メモ
.circleci/config.yml 内の階層を意識しつつまとめておく

- parameters: パイプラインパラメータ。config.yml内でどこからでも呼び出せる変数を定義できるグローバル変数っぽいやつ。
    - <パラメータ名>
        - type: パラメータの型
        - default: デフォルト値。ここを書き換えられるのは、APIでパイプラインを起動したときだけっぽい。というわけでAPIで起動しない場合は、parametersは変数っていうか定数というイメージでよさそう。
- jobs
    - ＜ジョブ名＞
        - parameters: ジョブレベルのparameterを定義する。イメージとしては、ジョブ実行時に渡せる引数の定義。引数はworkflowで渡す。
            - <パラメータ名>: パラメータ名
                - default: workflowでのジョブ呼び出し時に何も指定されなかった場合に採用されるパラメータの値。
                - type: パラメータの型。
        - docker
            - image: [cimg/base](https://circleci.com/developer/ja/images/image/cimg/base)というのをよく指定するが、これはCircleCIが作成したイメージである。CircleCI コンビニエンスイメージという意味らしい。ビルドを実行する際に便利なツールが含まれている。
        - working_directory: stepsを実行するディレクトリを指定。mkdir & cdに相当。
        - steps
            - checkout: SSHでGitリポジトリからプロジェクトソースをチェックアウトする。
            - run: シェルコマンドを実行する。
                - name: CircleCIのUI上で表示される「実行工程のタイトル」である。runに限らず、大体のステップで指定できる。何も設定しないと、各ステップのデフォルト名が表示される。
                - command: 実行するコマンドを記述。``command: |``として複数行書いていくのが主流か。長くなリすぎる場合はシェルスクリプト化してそれを実行するようにした方が可読性が高そう(拡張子によっていい感じに識別子を色分け化してくれるエディタを使っている場合は特に)。
                - working_directory: mkdir & cdに相当。以降のステップには影響しない。以降のステップに影響するやつがほしいんだけど、そういうのはないみたい・・・残念。
            - save_cache: キャッシュキーを指定して、任意のファイルをキャッシュとして保存できる。ただし、一度使ったキャッシュキーを指定しても、キャッシュを上書きはしてくれず、工程がスキップされる。保存したい場合は、一意のキャッシュキーになるよう工夫する必要がある([checksum を使ったり](https://circleci.com/docs/ja/caching/#using-keys-and-templates))。
            - restore_cache: キャッシュキーを指定して、キャッシュを復元できる。指定したキャッシュキーは前方一致で検索される。[複数のキャッシュがヒットする場合、最新のキャッシュが採用される](https://circleci.com/docs/ja/caching/#restoring-cache)。
            - store_artifacts: 指定したファイルを保存する。保存したファイルはCircleCIのUI上からDLできる。


- workflows
    - <ワークフロー名>
        - jobs
            - <ジョブ名>: ワークフローで実行するジョブ名
                - name: この実行ジョブの別名。requiresでもこの別名で指定できるようになる。CircleCIのUI上で表示されるジョブのタイトルでもあり、指定していない場合は<ジョブ名>が表示される。
                - <パラメータ名>: ジョブに渡すパラメータ値を指定。
                - requires: 先に実行完了していなければならないジョブ群を指定
                    - <ジョブ名>: 先に指定していなければならないジョブ名を指定
                - filters: ワークフロー実行対象のGitブランチ or タグを限定する場合に設定
                    - branches: ワークフロー実行対象のブランチを限定する場合に設定
                        - only: ワークフロー実行対象のブランチ名を正規表現で設定
                  


# 環境変数と parameter
環境変数 environment は以下に対して設定できる固定値である。

設定する階層|展開可能箇所|補足
---|---|---
organization|job以下|CircleCIのコンソール上で、organizationに紐づくcontextで複数の環境変数をcontext名と共に設定。workflowの定義でjobを列挙する際、contextキーでcontext名を指定してjobに渡す。環境変数というよりシークレットであり、一度値を設定するとその値は目視確認できなくなる
project|.circleci/config.yml内のどこでも|CircleCIのコンソールで、projectに対して環境変数を設定する。
job|job内の全step|
docker|コンテナ内(.circleci/config.yml上に展開するものではない)|circleci.yml内でなく、パイプライン実行時に作成されるコンテナ内に渡される環境変数の模様
step|各step内|stepをまたいでは設定できない。stepそれぞれでシェルが起動するため。環境変数を引き継ぐには、BASH_ENVに書き込んで、次回以降のstepでシェルが起動したときに自動で読み込ませるというような形にする。


.circleci/config上での値の展開は、`$環境変数名`でできる(dockerキー以外)。

一方、parameterは以下に対して設定できるデフォルト値を持つ変数である。
設定する階層|展開可能箇所|補足
---|---|---
pipeline|.circleci/config.yml内のどこでも|.circleci/config.yml内でトップレベルのparametersで設定する。デフォルト値からの変更チャンスはCircleCIパイプライン実行時のみ。APIの直接実行や、CircleCIのコンソール上で手動入力する形。したがって、通常利用(pushをトリガーとするパイプライン実行)ではブランチ名によって渡す値を変更するといったことはできない。
job|job直下の階層|
command|command直下の階層|

.circleci/config.yml上でのparameterの展開は`<< pipeline.<parameter名> >>`で。


**所感**
- シークレット情報(パスワード、他クラウドサービスを操作するためのサービスアカウントのキー等)はorganization, projectの環境変数で設定
- command, jobは適宜parametersを定義して、.cirleci/config.yml内で条件に応じた再利用をしやすく記述
- workflowに列挙するjobで, 条件に応じてjobにわたすparameterをベタ書きで切り替える

とするのが色々とすっきりしそう。


# stepの成否判定
CircleCI では、workflow, steps, run, save_cacheといった特定の場面でwhenを指定することで条件分岐できる。
このうち、run と save_cache では、「前のstep群がすべて成功`on_success`」か「どこかで失敗`on_fail`」か「常時`always`」のいずれかに該当したときにのみ処理を実行するという指定をする。これにより、処理結果に応じた分岐をさせることができる。(何も指定されていない場合はon_success扱い)

処理の成否判定は終了コードで判断されており、終了コード0: 成功、0以外:失敗と判断される。
command を定義する際にこれを意識しておかないと、「本当は失敗してるのに以降のstepを続行してしまう、成功しているのにstepが続行されない」といった期待しない動作となってしまう。(デプロイに絡むようなjobの場合、致命的な障害につながる可能性もあると思われる)


**参考**

https://circleci.com/docs/configuration-reference/#run