Wercker を使ってみた感想
Oracle Container Pipelines として取り込まれた CI・CD ツールである Wercker を使ってみたので紹介。
Wercker ってどんなもの?僕が感じた特徴まとめ
Wercker とはどんなものなのか。Jenkins を知っていればイメージはしやすいと思う。
- Git リポジトリへの Push をトリガーに発火する
- Git リポジトリ1つに対し、1つの Wercker プロジェクトを作る
- 実行するジョブ定義は Git リポジトリ内に格納した
wercker.yml
に記述する - ジョブ (Wercker では Pipeline と表現) の実行環境 (Box) は指定したコンテナが使われる
- ココが Jenkins との大きな違い。Jenkins の場合は「Jenkins をインストールしてあるサーバ」が実行環境になるので、Jenkins サーバに Python 入れといて Node.js 入れといて…といった手間が発生する
- Wercker はジョブ全体 (Workflows) もしくはジョブ単体 (Pipeline) ごとに仮想環境を立てられるので、環境がクリーンになる
- Pipeline の定義、Pipeline 同士の実行順序を決める Workflow は Wercker の管理画面 (ブラウザ) 上でしか定義できない
wercker.yml
は各 Pipeline の処理しか書けず、「次にどれを実行するか」といった指定はできない。ちょっと不便
- 環境変数は管理画面と
wercker.yml
のどちらでも指定可能- クレデンシャル情報は
wercker.yml
(Git リポジトリ) に含めない方が良いので管理画面で指定した方が良いと思うが、なんかこう、疎結合過ぎて関係性がフワッとしがち
- クレデンシャル情報は
wercker.yml
内に記述する処理 (Step) は任意のコマンドを実行したりできる他、誰かが作った独自 Step や自作の Step を用意したりできる- Wercker 管理画面右上から「Steps Store」というページに飛べるが、ココが DockerHub みたいな感じで、Wercker Step のリポジトリになっている
- Step 自体も Wercker で作成・構築できるので、複雑化してきた
wercker.yml
から Step を切り出して Publish しやすいだろう wercker.yml
にはいきなりどこぞの Step 名を書いて良い。Steps Store で確認できる公開済の Step を自動的に特定し実行してくれる
その他特徴をトピックごとに
実行環境がコンテナになる点、Step という概念が難しい。また、Workflow と環境変数が wercker.yml
と別れてしまうので、それぞれの関連性が把握しづらい感じがある。下手すると「作った人しか理解できない」Workflow になりそうなので、小さく作ろう。
Pipeline の名称は任意に決められるが、Wercker CLI は build
と deploy
という名前の Pipeline だけ特別に実行できたりするので、メインはこの2つ、ビルドしてデプロイする、という CD (Continuous Delivery) を意識したツールになっている。ココでいう「デプロイ」は、Docker コンテナイメージを成果物として Docker リポジトリに Push するまでにしたり、もしくは Kubernetes クラスタへのデプロイまでを行わせたりもできる (kubectl
コマンドを実行するための Step もある)。
各 Pipeline をまたいだ時、成果物ファイルはどうなるかというと、Workflow 全体でボリュームを共用しているので、「手前の build
Pipeline でビルドしたファイルを使って deploy
Pipeline を実行する」といった処理が可能。一つの Pipeline で全部をやる必要はない。しかし、npm install
のようにモジュールをインストールするような Pipeline の場合、次の Pipeline に移行した時にシンボリックリンクが破損することがあるようなので、Pipeline をまたいで使用するファイルは静的なファイルである方が安全。
あと、Wercker サービス全体としてはユーザ管理が微妙だ。Jenkins の場合、ジョブ定義は全メンバで共用でき、権限さえあれば誰でも編集できる関係だった。しかし Wercker は、Git リポジトリと Wercker アカウントが1対1で紐付いたような関係になってしまっている。
プライベートな Git リポジトリを紐付けたい時、ユーザ名とパスワードをアカウント設定に記述するため、そのユーザがパスワード変更を行うと認証が通らなくなり Pipeline が実行できなくなったりする。
Organization というグループを作る機能があるものの、コレに参加していない人は、wercker.yml
は変更できるのに、Workflows は変更できない、という中途半端な状態になる。Jenkinsfile
だったらこうはならないのだが…。
いずれにしても、「人」が作ったアカウントに紐付くため、「Jenkins 用アカウント」みたいなシステム利用するアカウントで逃げる方法が取れず、同じ考え方では不便するところが出てくると思う。
自分が作ってみた wercker.yml
感想はココまで。以降は、最初とっつきづらい wercker.yml
のイメージを知ってもらうため、自分が作ってみた wercker.yml
を紹介する。
以下の例は、
- Node.js アプリを Lint → UT → コンパイルする
build
Pipeline と、 - Docker ビルドしてプライベートな Docker レジストリにイメージを Push する
deploy
Pipeline
の2つから構成されている。
box:
id: node:10-stretch
build:
steps:
- npm-install
- script:
name: lint
code: npm run lint
- npm-test
- script:
name: build
code: npm run build
deploy:
steps:
- internal/docker-build:
name: build image
dockerfile: ./Dockerfile
image-name: my-container
- internal/docker-push:
name: push image
image-name: my-container
username: $REGISTRY_USERNAME
password: $REGISTRY_TOKEN
repository: iad.ocir.io/my-tenancy/my-ocir/my-project
tag: v1
各行にコメントを足してみた詳説版。
# Box
#
# 各 Pipeline の実行環境となる Docker イメージ。DockerHub から自動的に取得してくれる
box:
# ココでは Node.js v10 系が動作するイメージを選択した
id: node:10-stretch
# Build
#
# - Lint・UT を実施する
# - 成果物ファイルをビルドする
build:
steps:
# この「npm-install」Step は Wercker 公式が提供する Step
# - 参考 : https://app.wercker.com/steps/wercker/npm-install
# - 参考 : https://github.com/wercker/step-npm-install
- npm-install
# 「script」Step は、シェルスクリプトで任意のコマンドを実行できる
# npm コマンドが使える Box を指定しているので、以下のように「npm run」が叩ける
# package.json の scripts で定義した「lint」という npm-run-scripts を実行して Lint している
- script:
name: lint
code: npm run lint
# 「npm run test」「npm test」と同義
- npm-test
# ソースファイルのビルド。「dist」ディレクトリにでも成果物が吐かれているテイ
- script:
name: build
code: npm run build
# Deploy
#
# - Docker イメージをビルドする
# - プライベート Docker レジストリに Push する
deploy:
steps:
# Docker ビルドを行う
- internal/docker-build:
name: build image
dockerfile: ./Dockerfile
# 「image-name」は、次の「internal/docker-push」Step で対象イメージを紐付けのための一時的な名前
image-name: my-container
# Docker Push を行う
- internal/docker-push:
name: push image
# 前述の「image-name」を指定する
image-name: my-container
# 以下の環境変数は管理画面で設定しておく
username: $REGISTRY_USERNAME
password: $REGISTRY_TOKEN
# Docker レジストリの URL を指定する。以下は Oracle Cloud Infrastructure Registry を使うテイ
repository: iad.ocir.io/my-tenancy/my-ocir/my-project
# Docker イメージのタグ名
tag: v1
実装してみたのはこんな感じ。
Docker タグ名を変更したい時のアイデア
上述の wercker.yml
の最終行を見ると、tag: v1
と、Push する Docker イメージのタグ名がベタ書きになっている。このままだと、イメージ名を変更したい場合はこの wercker.yml
のベタ書きな行を変更して git push
しないといけなくなってしまう。少々面倒で、忘れてしまいやすい。
そこでアイデアとして考えられるのは、Git のブランチ名からバージョン番号を抜き出して利用する、という方法。例えば release/v1
・release/v2
といったブランチを必ず作るルールにしていれば、この release/
以降の文字列を抜き出して使用できれば良いワケだ。
Wercker の実行環境には、Wercker が自動的に設定してくれる環境変数がいくつかあり、トリガーとなった Git ブランチ名は $WERCKER_GIT_BRANCH
という環境変数で読み取れる。コレで取得したブランチ名を整形してタグ名を作り、export
コマンドで環境変数として残してやれば、次の Step で参照できる。
以下にサンプルを掲載する。
deploy:
steps:
# 環境変数 $WERCKER_GIT_BRANCH から「release/」部分を除去し、Docker タグ名として使えるように環境変数 $TAG_NAME に設定する
# ブランチ名が「release/」始まりでないブランチはそもそもこの Step を実行しなくて良いので異常終了とする
- script:
name: create image tag name
# コードを複数行に渡って書きたい場合は、以下のようにパイプ記号を入れてから改行していけば書ける
code: |
if $(echo "${WERCKER_GIT_BRANCH}" | grep '^release\/*' > /dev/null) ; then
echo "This is release branch ... ${WERCKER_GIT_BRANCH}"
else
echo "This is not release branch. Abort ... ${WERCKER_GIT_BRANCH}"
exit 1
fi
export TAG_NAME=$(echo "${WERCKER_GIT_BRANCH}" | sed -e 's:^release/::')
echo "Tag name ... ${TAG_NAME}"
- internal/docker-build:
# Docker Build : 中略
- internal/docker-push:
# 他の項目は中略
# Docker イメージのタグ名として、先ほど export した環境変数 $TAG_NAME を使用する
tag: $TAG_NAME
この deploy
Pipeline は release/
で始まるブランチに対する Push 時のみ実行できれば良いので、Wercker 管理画面の Workflows 定義にて、実行するブランチの条件を設定しておく。シェルスクリプトでの異常終了処理はあくまで安全のため。
なお、GitHub 以外の Git リポジトリと接続している場合、ブランチ名に含まれるスラッシュ以前の文字列が欠落する不具合を確認しているので、Wercker の挙動に合わせて、スラッシュではなくハイフンで繋ぐなど、ブランチ命名規則は要検討。
Kubernetes クラスタにデプロイするには
wercker.yml
から Kubernetes クラスタにデプロイするにはどうしたらいいかというと、以下のページに掲載されているコードが参考になるだろう。
deploy:
steps:
- kubectl:
name: apply deployment
server: $KUBERNETES_MASTER
token: $KUBERNETES_TOKEN
insecure-skip-tls-verify: true
command: apply -f $WERCKER_ROOT/kubernetes/deployment.yaml
こんなノリで kubectl
コマンドが叩ける。ココで使っている kubectl
Step も Wercker 公式。
server
プロパティに指定するのは kubeconfig
ファイル中の clusters.cluster.server
の値 (URL)、token
プロパティに指定するのは kubeconfig
中の users.user.token
の値。単純に kubectl
コマンドが叩けるユーザのクレデンシャル情報を引っ張ってきているだけ。これらの値は環境変数として退避しておく。
deployment.yaml
の中には、デプロイしたい Docker イメージ名を書くことになる。予め書いておいて git push
するでも良いが、ベタ書きだと wercker.yml
の中の値と一致させないといけないので、オペミスの危険がある。そこで、先程環境変数 $TAG_NAME
を編み出したコードを活用して、deploy
Pipeline 中で deployment.yaml
を置換してしまうという方法が取れるだろう。
deployment.yaml
の記述は、以下の状態で固定しておく。
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-deployment
spec:
selector:
matchLabels:
app: my-app
replicas: 3
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-container
# ダミーのタグ名を記述しておく
image: iad.ocir.io/my-tenancy/my-ocir/my-project:__TAG_NAME__
# 以下略……
タグ名の部分は後で置換するので、ダミー文字列 __TAG_NAME__
と書いておく。
続いて wercker.yml
。
deploy:
steps:
# 環境変数 $TAG_NAME を作っておく
- script:
name: create image tag name
code: |
if $(echo "${WERCKER_GIT_BRANCH}" | grep '^release\/*' > /dev/null) ; then
echo "This is release branch ... ${WERCKER_GIT_BRANCH}"
else
echo "This is not release branch. Abort ... ${WERCKER_GIT_BRANCH}"
exit 1
fi
export TAG_NAME=$(echo "${WERCKER_GIT_BRANCH}" | sed -e 's:^release/::')
echo "Tag name ... ${TAG_NAME}"
- internal/docker-build:
# Docker Build : 中略
- internal/docker-push:
# Docker Push : 中略
# 環境変数 $TAG_NAME を利用して deployment.yaml を置換・上書き保存する
- script:
name: edit deployment file
code: |
sed -i -e "s/__TAG_NAME__/$TAG_NAME/" $WERCKER_ROOT/kubernetes/deployment.yaml
# 置換したファイルを使って Kubernetes クラスタにデプロイする
- kubectl:
name: apply deployment
server: $KUBERNETES_MASTER
token: $KUBERNETES_TOKEN
insecure-skip-tls-verify: true
command: apply -f $WERCKER_ROOT/kubernetes/deployment.yaml
こんな感じで、deployment.yaml
を sed
で置換し、それを使って kubectl apply
を叩けば良いだろう。
じゃあブルー・グリーン・デプロイをするには?service.yaml
も置換して kubectl apply
しようか。その後残った古い Deployment をどう消そうか?…考え出すとつらい。そもそも「時代は CI・CD だ!」と言いながら「リリース予定日」を決めないと動けなかったりするのが実際のところなので、kubectl
を叩くような wercker.yml
は書かないことにした。手動でやろう。
Docker ビルドと Push だけにするにしても、コレも高頻度でやられてしまうと困る、という場合は、「Docker ビルドを行うためのブランチ名規則」を決めておいて、ビルドしたい時だけそのブランチ名で git push
してやる、という方法が取れるだろう。Werkcer は git tag
の Push にも対応しているのだが、現状は連携先が GitHub リポジトリの場合しか使えないので、仕方なく「ビルド用ブランチ」という考え方を取ることにした。
# こんな風にリモートに Push するブランチ名を変える
$ git push origin master:build-v1.2
# リモートブランチが要らなくなったら以下のようにすれば消せる
$ git push origin :build-v1.2
以上
Wercker にしても Jenkins にしても、「自動化できる」といえばできるんだけど、それは「技術的には出来る」までの意味でしかない。自動化するための手順を作っていくにはやっぱり人が作る必要があって、何度実行してもおかしくならないような冪等性を担保したり、特定のエラーをハンドリングしたり、といったことを始めると物凄く大変になる。そうして作り上げた「匠の技盛り合わせスクリプト」は、Jenkinsfile だろうと YAML だろうとシェルスクリプトだろうと、言語に限らず肥大化・複雑化していき、管理しきれなくなる。
チーム開発では特に問題になりがちなので、「まずどんな業務があるか」を洗い出し、できればその時点で不要な業務を「やらない」選択を取れるよう、業務を整理した方が良い。その上で、「実装コスト・その後のメンテナンスのコストを加味しても自動化するに値する業務」のみ自動化した方が良いだろう。条件が定量化できなくて自動化しづらいような業務は、潔く「自動化しない」という選択を取ることも重要。
自動化した後に本当に「楽」を感じられるように、小さく CI・CD 環境を作ろう。