tech.guitarrapc.cóm

Technical updates

CircleCI Orb をいくつか作った話

幾つか CircleCI Orb を作っていたのですが、記事にしていませんでした。

どれも必要になって作ったもので、いずれもプロダクションで万単位で実行されれているのでいい感じで使えそうなら幸いです。

目次

TL;DR

  • NuGet Cache を制御するなら guitarrapc/nuget-cache
  • Shallow Clone を制御するなら guitarrapc/git-shallow-clone
  • Chatwork にメッセージを投げるなら guitarrapc/chatwork
  • CircleCI は、公式Orb でやっているやり方をドキュメントに反映してほしいです。Orbを初めて作るときに把握から始まるのは結構ツライ

Orbs とは

CircleCI は Job や Command にまとめていた処理を Orb として共有できます。

circleci.com

これって結構強力でいい仕組みで、Executor を含んだり含まないことができます。

  • CircleCI でYAMLが特定の処理のためにやけに長くなってしまった (Executor 依存がないとよりよい)
  • 特定の操作を行いたい

特定のシーンにスコープを絞れば Executor 依存してもいいし、特定の処理に絞って汎用的に使いたいなら Executor 依存しないようにするといいでしょう。

CircleCI でやっていこうと思ったら、何かしたいときは Orb をチェックするのは標準的に行うといい流れを作ろうとしているし、実際そうだと思います。

既存のOrbs で提案があれば適当にPR出したりもいいでしょう。 どのみち config.yml は公開されますしね。

tech.guitarrapc.com

公開したOrbs

3つ作って公開しています。

どれも当初は commands として定義していたのですが、ほかのプロジェクトでも使えるし公開しました。

guitarrapc/git-shallow-clone

https://circleci.com/orbs/registry/orb/guitarrapc/git-shallow-clone

git shallow clone をするOrb は実はいくつかあります。 有名なのは、ganta/git でしょうか。

qiita.com

しかしこれらのOrb はどれも Fork プロジェクトは考慮されていません。 また、GitHubや BitBucket が死んでることを想定していないので、案外失敗します。

guitarrapc/git-shallow-clone は、Fork プロジェクトにも対応しつつ、実際に頻繁に使っても問題ないまでブラッシュアップしてあります。

利用はいたってシンプルです。

orbs:
  git: guitarrapc/git-shallow-clone@1.0.1
version: 2.1
workflows:
  build:
    jobs:
      - git/checkout_shallow

keyscan_bitbucket と keyscan_github で、実際のリポジトリを keyscan するか選べます。 デフォルトは、鍵の更新がしばらくないのでハードコードしてあります。

depth と fetch_depth で、深さを指定できます。 Shallow Clone は、depth で 1指定しておいて、fetch depth では少し深く 5~10指定しないとブランチとかをいい感じで処理できないのでその対策も入っています。 clone時にsingle branchを使ってると気にせずにすむのですが。

clone を高速に終わらせて、汎用的に使いたいならお勧めです。 毎日結構な回数動いてて安定しているので、大概のケースでいけるかと。

この Orb はテストを書くのが面倒でしたが、ひとこねしたテストも書けるのは CircleCI 強かった。

guitarrapc/nuget-cache

https://circleci.com/orbs/registry/orb/guitarrapc/nuget-cache

.NET Core のビルドで困るのが、nuget package の restoreサイズは大きく時間がかかるということです。 別に一切気にしないという手もありますが、キャッシュしておくほうが爆速になるのでやるでしょう。

ただ、cache をするにはキャッシュキーを決める必要があります。 利用するパッケージに変更があったらキャッシュを切り替えたいわけです。 .NET Core なら csprojの変化を見るのが一番間違いがないでしょう。(Directory.Build.props は考慮したくないですね!)

guitarrapc/nuget-cache を使うと、任意のファイルを拾って md5 を取得してキャッシュキーに利用しつつ、cache のsave/restoreが可能になります。

想定している流れ場、nuget_cache/nuget_restore_cache して、dotnet restore してnuget_cache/nuget_save_cacheです。

orb でいい感じのキーで nuget-restore して、dotnet cli でdotnet restore するのはキャッシュがあれば秒で終わります。 キャッシュがずれていたら適切に dotnet restoreでリストアされるので、あとはいい感じのキーでsave してあげれば次回以降のビルドで生きます。

orbs:
  nuget-cache: guitarrapc/nuget-cache@0.1.1
version: 2.1
executors:
  dotnet31:
    docker:
      - image: 'mcr.microsoft.com/dotnet/core/sdk:3.1'
    environment:
      DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
      NUGET_XMLDOC_MODE: skip
jobs:
  build:
    executor: dotnet31
    steps:
      - checkout
      - nuget_cache/nuget_restore_cache:
          cache_key: test_save_cache
          project: nuget-cache-orb
      - run: dotnet restore
      - nuget_cache/nuget_save_cache:
          cache_key: test_save_cache
          project: nuget-cache-orb
          project_cache: false
      - run: dotnet build

シンプルですが、こういうのが適当にできるのがいいなぁということで作りました。 いろんなプロジェクト構成に対応できるように、cache_key や project、working fidrectoryや target file pattern も指定できるようにしてあります。

実際、外部リポジトリを使った複雑なプロジェクト構成でも機能しているのでだいたいうまくいくはずです。

guitarrapc/chatwork

https://circleci.com/orbs/registry/orb/guitarrapc/chatwork

Chatwork のルームにメッセージを送信します。 さくっとメッセージを送るだけなら、Chatwork API を使うのがいいでしょう。(こんなことで OAuthしたくない)

ということで、curl でサクッと送ります。

orb:
  chatwork: guitarrac/chatwork@0.0.2
version: 2.1
workflow:
  build:
    jobs:
      - chatwork/notify:
          message: Hello+Chatwork%21
          room_id: '12345'

環境変数 CHATWORK_TOKEN に API Token だけ仕込んでおけばサクッと使えます。 また、when に対応してあるので、「on_success 成功時 (デフォルト) 」、「on_fail 失敗時」、「always いつでも」の状況に合わせた送信ができます。

Orb を作る

基本は公式を見ましょう。

circleci.com

namespace とか、circleci cli の基本操作は沿っておく必要があります。

ただし、公式 CircleCI Orb はいずれもこの構成とはずれています。ひどい。 ということで、実際どうやって構成しているのかを把握します。

Orb の構成を把握する

Orb を使うのはともかく、作るとなると「作ること自体は簡単」です。 ただ、「テストやCI/CD」まで含めると面倒ですが、テンプレート化してしまえばあとは流れ作業で作れます。

このテンプレート化がドキュメントがなく、CircleCI 公式Orb でもどれもバラバラなので把握が面倒です。

公式を見つつどんな構造化把握します。

基本構造

公式Orbもそうなのですが、Orb のディレクトリは基本的に次の構造になるはずです。

.
├── .circleci
│   └── config.yml
├── LICENSE.md
├── README.md
├── src
│   ├── commands
│   │   └── your_command.yml
│   ├── examples
│   │   └── your_command.yml
│   └── @orb.yml
└── tests
    ├── your_test.yml
    └── your_test_n.yml

シンプルに、@orb.yml に全部定義というのもいいと思います。

github.com

.circleci

Orb自体のCI/CDは CircleCI 自身で行います。後述。

src/@orb.yml

Orb のDescription と依存する Orb などを書きます。

例えば、次のように書く

version: 2.1

description: >
  Build images and push them to the Amazon Elastic Container Registry.
  See this orb's source: https://github.com/circleci-public/aws-ecr-orb
orbs:
  aws-cli: circleci/aws-cli@0.1.13

Orbのページでは次のように表示されます。

実際のところ、@orb.yml じゃなくても orb.yaml で動作します。 私は orb.yml にしていますが、公式はみんな @orb.yml なのでそっちにしておけばいいでしょう。

src/commands/

commandsフォルダの中に置いたファイル名がコマンド名になります。 いいですね、この割り切り。

ecr-login というコマンドなら、ecr-login.yml というファイル名にします。

description: "Authenticate into the Amazon ECR service"

parameters:
  region:
    type: env_var_name
    default: AWS_REGION
    description: >
      Name of env var storing your AWS region information,
      defaults to AWS_REGION
steps:
  - run:
      name: Log into Amazon ECR
      command: |
        # aws ecr get-login returns a login command w/ a temp token
        LOGIN_COMMAND=$(aws ecr get-login --no-include-email --region $<<parameters.region>>)
        # save it to an env var & use that env var to login
        $LOGIN_COMMAND

すると、ecr-login としてコマンド定義が公開される。

src/examples/

コマンドのサンプルを定義します。 examplesフォルダの中に置いたファイル名がExample名になります。

なのでコマンドごとにファイルを用意したり、Workflow として一連の流れを定義したりです。

たとえば Workflow simple-build-and-push-image を定義して、それを Orb画面で simple-build-and-push として表示したいなら次の定義になります。

description: Log into AWS, build and push image to Amazon ECR

usage:
  version: 2.1

  orbs:
    aws-ecr: circleci/aws-ecr@x.y.z

  workflows:
    build_and_push_image:
      jobs:
        # build and push image to ECR
        - aws-ecr/build-and-push-image:

            # required if any necessary secrets are stored via Contexts
            context: myContext

            # AWS profile name, defaults to "default"
            profile-name: myProfileName

            # name of env var storing your AWS Access Key ID, defaults to AWS_ACCESS_KEY_ID
            aws-access-key-id: ACCESS_KEY_ID_ENV_VAR_NAME

            # name of env var storing your AWS Secret Access Key, defaults to AWS_SECRET_ACCESS_KEY
            aws-secret-access-key: SECRET_ACCESS_KEY_ENV_VAR_NAME

            # name of env var storing your AWS region, defaults to AWS_REGION
            region: AWS_REGION_ENV_VAR_NAME

            # name of env var storing your ECR account URL, defaults to AWS_ECR_ACCOUNT_URL
            account-url: AWS_ECR_ACCOUNT_URL_ENV_VAR_NAME

            # name of your ECR repository
            repo: myECRRepository

            # set this to true to create the repository if it does not already exist, defaults to "false"
            create-repo: true

            # ECR image tags (comma separated string), defaults to "latest"
            tag: latest,myECRRepoTag

            # name of Dockerfile to use, defaults to "Dockerfile"
            dockerfile: myDockerfile

            # path to Dockerfile, defaults to . (working directory)
            path: pathToMyDockerfile

Orb画面は、simple-build-and-push とファイル名になります。

tests

テストを定義しておきます。

ここは CircleCI 上で解釈されて実行されるので、いい感じにファイルを置きます。 もちろん CircleCI 上で config.yaml を走らせることもできて、その場合はconfig.yaml の記法に沿っておく必要があります。

公式Orbは案外tests フォルダを持っていないのですが、devに挙げたorbで挙動をテストするようにしています。

testsフォルダを設けている例として、mafuyuk さんや私のOrb を例にします。

github.com

github.com

例えば、mafuyuk/terraform-orb は terraform 操作を行う Orbです。 その test.yml は次のようになっています。

  version: 2.1

jobs:
  build:
    executor:
      name: terraform/default
      tag: 0.11.11
    steps:
      - checkout
      - run:
          name: Create Sample Terraform
          command: |
            mv test/backend.tf backend.tf
            mv test/main.tf main.tf
            mv test/provider.tf provider.tf
            mv test/variables.tf variables.tf
#      - terraform/init
#      - terraform/plan
#      - terraform/apply:
#          use-plan: false

コメントアウトされていますが、ニュアンスはわかります。

テストのときは、cache のようなファイル操作ができないのは circleci cli の制約を食らうのでそこは注意です。 なんだそれ感はある。

もし、外部リポジトリをクローンするなどSSH Key を渡したりといった込み入ったことをする必要があるなら、こっちのOrbが参考になるでしょう。

github.com

ポイントは、テストをどう走らせるかです。

  test:
    # ref: https://github.com/ganta/git-orb/blob/master/.circleci/config.yml
    # Workaround for passing a SSH key with orb-tools/local-test-build command
    # https://github.com/CircleCI-Public/orb-tools-orb/blob/330d11bb0cdef3c2f24cb4e2c595ca01e55bfd8c/src/%40orb.yml#L107-L134
    commands:
      local-test-build:
        parameters:
          path:
            type: string
        steps:
          - run: cp << parameters.path >> tmp-config-src/config.yml
          - run: mkdir -pv $(dirname tmp-config-src.yml)
          - run: circleci config pack tmp-config-src > uncompiled-config.yml
          - run: cat uncompiled-config.yml
          - run: circleci config process uncompiled-config.yml > config.yml
          # Cannot access "~/.ssh" from working directory.
          # pass spin up env's CIRCLE_TAG to local circleci command.
          - run:
              command: |
                cp ~/.ssh/id_rsa ./id_rsa
                circleci local execute --checkout-key ./id_rsa -c config.yml -e CIRCLE_TAG=${CIRCLE_TAG} | \
                  tee local_build_output.txt /dev/stderr | \
                  tail -n 1 | \
                  grep "Success"

Orb のCI/CD をする

Orbを作ったら、それをいい感じでCircleCI 上で Orb のCI/CD をしたいでしょう。

CI/CDでは当然、Orb のLint、テストをしたり、Orb Dev に挙げて動作を事前テストしたり、Devから Production に Promote したりしたいです。 Promote はGitHub Tag に準じるといいでしょう。

つまりこれ。

  • CircleCI Orbで提供する処理を定義する
  • CircleCI Orbで何ができるのかDescriptionやExampleを定義する
  • Orb のCIを行う
  • Orb のDevへのCDを行う
  • Orb のProduction への CD (Promote) を行う

CircleCI-Public/orb-tools-orb

Orbリリースをするためのツールとして CircleCI が用意しているのが CircleCI-Public/orb-tools-orb です。

github.com

これを使うと、YAML のLintチェックから、パッケージング、テストの実行、GitHub Release 連動、DevへのCD、ProductionへのCD まで一通りできます、便利! ドキュメントがなく Example だけで動作もわかりにくいですが、がんばれ。

qiita.com

9.x 系

orb-tools-orb ですが、9.0.0でコマンドを悉く変えています。 9系はコミットでの制御に切り替える方針に伴って破壊的変更をしていて、コマンド自体が消えたりパラメーターが丸っと変わっています。

9.0.0 をやってるケースもあるので、気が向いたらあげよう。

qiita.com

CircleCI 公式の各種Orb は8.27.3 以前のバージョンで止まっていたりします。 対応確定するまでは 8.27.3 をターゲットで私は書いています。(8.27.6時点でもうダメだったりする)

基本的なパターン

公式Orb を参考にするといいでしょう。

circleci-orbs/config.yml at master · CircleCI-Public/circleci-orbs

slack-orb/config.yml at staging · CircleCI-Public/slack-orb

aws-ecr-orb/config.yml at master · CircleCI-Public/aws-ecr-orb

8.27.3 をターゲットにします。

定義

公式Orb 各種がそうですが、概ね次の流れです。

slack-orb/config.yml at staging · CircleCI-Public/slack-orb

lint_pack-validate_publish-dev

PR へのプッシュでトリガーします。

  • orb-tools/lint
  • orb-tools/pack
  • orb-tools/publish-dev
  • orb-tools/trigger-integration-workflow
  • orb-tools/trigger-integration-workflow

integration_tests-prod_deploy

テストはintegration タグやmaster-タグでトリガーします。 dev-promote-prodは、各種master-patch / master-minor / master-major のタグでトリガーされます。

  • 各種テスト
  • orb-tools/dev-promote-prod (patch)
  • orb-tools/dev-promote-prod (minor)
  • orb-tools/dev-promote-prod (major)

挙動は config.yaml だけ見ても全然分からず、動かしたりCircleCI-Public/orb-tools-orb をみるしかないでしょう。

リリースの流れ

PR を作って、CI と dev への CD が走るので通るのを確認、でマージすると Orb にpush されます。

Renovate を使うという手も。

www.kaizenprogrammer.com

Orbのテンプレートにする

適当に guitarrapc/chatwork-orb あたりはシンプルなのでテンプレートにしやすいでしょう。

github.com

Orbのnamespace を用意したら、これを fork したり適当にベースにして、OrbのコマンドとExample と orb.yml を変えてテスト書けば行けるはずです。(orb名やテストの置換はしましょう)

TIPS

既存の Orb で時々使いにくいやつがあるので、そうならないようにメモ。

  • Jobとしての提供なら、Orb でExecutor 指定しよう
    • 案外こういうのは多いので、どう使いたいか次第でまず決めるといい
  • Stepsとしての提供なら、Orb はなるべく依存小さくするほうがいい
    • Bash依存とか避けたほういいのは間違いない
    • コマンドある前提で組んでしまって、コマンドないならサクッとエラーになるのでユーザーに任せるほうがユーザーにとっても幸せ

REF

qiita.com

www.kaizenprogrammer.com

sue445.hatenablog.com