k8s上でdockerイメージをbuild&pushする
個人で開発用の環境を持つことを考えた時、有力な選択肢にgitlabの構築が挙げられます[1]。
今回はk8s上で、gitlabのリポジトリ上で管理されているdockerfileをdocker build & push
し、コンテナレジストリにpushする機構 = k8s上にdocker build
可能なgitlab-runner Podを、kubernetesの機能のみ用いてデプロイします。(helm等のマネジメントツールは不要です。)
gitlabについて
gitlabはgithubをプライベートクラウド/ローカルで構築することができるパッケージです。
クラウド上でのコードの管理・チーム開発などの基本機能の他、コンテナレジストリの構築やCI/CDなど、多くの拡張機能によってカスタマイズが可能です。gitlab-runnerについて
gitlab-runnerはgitlabで発行されたjobを受信し、CIを実施するための機構です。
gitlabリポジトリへのpushを検知し、あらかじめ定義されたコマンドを各executorを用いて実行することができます。
gitlab-runnerはexecutor
を実行してコマンドの実行を実現します。
今回はgitlab-runner内でdocker
コマンドを実行できる、docker
コンテナイメージを用いて、docker executorを実行するという流れになっています。
アクセストークン登録の自動化
gitlabは、登録済みのgitlab-runnerに対してリクエストを許可する仕組みになっているため、runnerごとにアクセストークンを発行する必要があります。
アクセストークンの発行はrunner側で登録時に手動で実施する必要があるのですが、k8sの機能、initコンテナ・LifecycleEventsを用いることで、この部分を自動化することができます。
initコンテナとは
initコンテナはPodをメインで構成するコンテナが動作する前に実行されるコンテナです。
メインコンテナで利用するファイルをあらかじめ作成しておいたり、セットアップを実行させることができます。
LifecycleEventsとは
LifecycleEventsは、Pod内のコンテナに対して特定のタイミングで、コマンドを実行することができる機能です。
コンテナのプロセスはルートプロセスとして実行されますが、LifecycleEventsは同コンテナ別プロセスとして実行されます。
initコンテナとLifecycleEventsの違い
両者の特徴は以下のようになります。
-
initコンテナ
- Podに対して設定、Podに設定されたコンテナの前にデプロイされる。
- initコンテナが失敗した場合は、メインコンテナは実行されない。
- initコンテナは終了する必要がある。initコンテナ終了後はメインプロセス(コンテナ)からは隔離。
- k8sの
template.spec.containers
の要領でマニフェストを記述できるため、volumeマウントなどが可能 - このため、主にメインコンテナで使用する必要のあるデータなどを作成するために使用される。
- k8sの
-
LifecycleEvents
- コンテナに対して設定、postStartとpreStopが設定可能。
- postStartはコンテナのルートプロセス実行前に実行。
- preStopはコンテナ終了前に実行。
- postStart/preStop失敗時はコンテナは強制終了される。
-
LifecycleEventsはコンテナのサブプロセスとして実行される。
- postStartはルートプロセスより後に実行される可能性がある。
- preStopはPod自体の終了でも稼働するが、処理の終了前にPodが削除される可能性もある。
-
template.spec.terminationGracePeriodSeconds
(v1.20~)の設定により終了処理用の時間確保が可能
-
- 実行分類によってはシェル/インタープリターを必要とする(exec)
- コンテナに対して設定、postStartとpreStopが設定可能。
gitlab-runnerでの利用
gitlab-runnerでは、初期トークンの作成をinitコンテナで実施し、configを作成します。本編のコンテナでここで作成されたconfigを使用。gitlab-runnerが不正停止された場合、preStopの実行によりgitlab上のgitlab-runnerのリストから除外することができます。
構築方法
動作の概要
本記事で構築するマニフェストは、次のような仕組みで動作します。
gitlabの構築
gitlabは以下の方法で構築できます。k8s on RaspberryPiでHAクラスタ構築にて、ansibleを用いて構築しています。
今回はgitlabの初期トークンの固定化
通常、registerトークンについてはgitlabのWebUIを見なければregisterトークンがわかりません。
ただし、gitlab初期構築時の場合は、configに記載することでアクセストークンを固定化することができます。
/etc/gitlab/gitlab.rb
の任意の場所に以下を追加します。
gitlab_rails['initial_shared_runners_registration_token'] = 'gitlabtoken'
設定変更したらgitlab-ctl reconfigure
します。
dockerのインストール
今回はDooD(docker out of docker)での構築を行うため、gitlab-runnerを実行するノードにdockerをインストールが必須になります。[2]
マニフェストのデプロイ
initコンテナで設定ファイルを作成し、メインコンテナから設定ファイルの存在するディレクトリをマウントするという流れをマニフェストに記述します。
apiVersion: apps/v1
kind: Deployment
metadata:
name: container-builder
spec:
replicas: 2 # レプリカを用意すると、それぞれのPodがregisterされるため、並列実行できます。
selector:
matchLabels:
name: container-builder
template:
metadata:
labels:
name: container-builder
spec:
nodeSelector:
interface: ethernet
initContainers:
- name: init-runner
image: gitlab/gitlab-runner:latest
command:
- gitlab-runner
- register
- --non-interactive
- --url=http://<gitlabのURLを入力>/
- --registration-token=<gitlabから確認できるregister-tokenを入力>
- --description=container-builder
- --tag-list=docker
- --executor=docker
- --docker-image=docker:latest
- --docker-network-mode=host
- --docker-volumes=/var/run/docker.sock:/var/run/docker.sock
# - --docker-extra-hosts=<urlが名前解決できない場合はIPアドレスでURLを設定>
volumeMounts:
- name: config
readOnly: false
mountPath: /etc/gitlab-runner/
containers:
- image: gitlab/gitlab-runner:latest
name: gitlab-runner
ports:
- containerPort: 80
name: http
- containerPort: 443
name: https
volumeMounts:
- name: config
mountPath: /etc/gitlab-runner/
readOnly: false
- name: socket
mountPath: /var/run/docker.sock
readOnly: true
lifecycle:
preStop:
exec:
command: ["gitlab-runner", "unregister", "--all-runners"]
volumes:
- name: config
emptyDir: {}
- name: socket
hostPath:
path: /var/run/docker.sock
restartPolicy: Always
上記をkubectl apply -f
すれば、gitlabにrunnerが登録され、CIの実行準備が完了です。
.gitlab-ci.ymlの作成
gitlabでCIを実行するためには、gitリポジトリのルートディレクトリに次のようなファイルを設置します。
stages:
- build
sample-app:
tags: [docker]
stage: build
script:
- docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN $CI_REGISTRY
- docker build ./${CI_JOB_NAME} -t "${CI_REGISTRY_IMAGE}/${CI_JOB_NAME}:${CI_COMMIT_TAG:=latest}"
- docker push "${CI_REGISTRY_IMAGE}/${CI_JOB_NAME}:${CI_COMMIT_TAG:=latest}"
上記は、pushされたgitlabのコンテナレジストリに対して<registryURL>/<gitプロジェクト名>/sample-app:latest(tagがpushされた場合はtag)
で登録処理を行うサンプルになります。
${CI_***}
となっている環境変数はgitlabの組み込み変数で、環境に応じた値が自動的に代入されます。
以上で設定は完了です。
今後の課題
今回はコンテナのビルドを実行可能な機構の構築方法について考えた結果、kubernetesのinitコンテナ・およびLifecycleEventsの利用を行い、gitlab-runnerを用いて実現することができました。
gitlab CI/CDによる工程の自動化はビルドに限らず、テストやデプロイも存在するため、今後はこちらをどのように実装するかを考えていくことになりそうです。
参考
-
プライベートクラウド/ローカル上の制約がない場合はgithubも選択肢となります。CI/CDについてはGitHub Actionsで実現可能です。 ↩︎
-
DinD(docker in docker)も手法としては存在しますが、docker-daemonプロセスを別途実行する必要があり、kubernetesのCRIとの二重実行でリソースを無駄に消費してしまうため、今回は選択肢になりませんでした。 ↩︎
Discussion