こんにちは、テクマトリックスの酒井です。
前回の記事ではローカル環境に Kubernetes クラスターと CI 環境を構築しました。そのときはビルド環境として Docker Hub で公開されている maven
などのコンテナイメージを利用しましたが、実際の開発では自分たちでコンテナイメージを用意することが多いのではないかと思います。そこで今回は、前回の記事で構築した CI 環境をベースに、ビルドに使用するコンテナイメージをビルドする CI 環境を作ってみます。
目次
なぜコンテナイメージをビルドするのか
この CI 環境では、すべてのビルド/テストをコンテナで実行したいと考えています。
ビルド/テストを実行するには、自分たちが使用するコンパイラ、ライブラリ、ビルド/テストツールなどがインストールされた環境(ここでは「ビルド/テスト環境」と呼びます。)が必要です。そしてビルド/テスト環境をコンテナで実行する際、ビルド/テストの実行の都度コンパイラなどをインストールして実行するのは無駄が多いと思います。反対にあらかじめコンパイラなどがインストール済みのコンテナイメージを作成しておくことで、インストールの時間を省き、ビルド/テストをより短時間で実行可能になります。
また CI 環境が社内に浸透して多数のビルドが実行されるようになれば、 Kubernetes クラスターを増強(マシンを追加)することにより、一つのコンテナイメージから多数のコンテナを並列で実行し、多数のビルドの要求をさばくことができます。社内で利用されるすべてのビルド/テスト環境のコンテナイメージを用意することができれば、一つの CI 環境で多数のビルド/テスト環境、多数のプロジェクトをサポートすることが可能になります。
なぜ Docker を使わないのか
Docker はコンテナイメージのビルドのためにもコンテナのランタイム(デーモン)を必要とします。しかしデーモンは通常特権を持つ必要があるため、 Kubernetes 上でデーモンをセキュアに実行することは簡単ではありません。
この課題を解決するアプローチとツールがいくつも存在するようですが、ここでは CloudBees CI という Jenkins をベースにした製品のドキュメントに記載のある Kaniko を利用してみます。ちなみに Kaniko のドキュメントには他のツールとの比較が記載されていますので、他のツールに興味がある方は参考にしていただければと思います。
構成
全体像
今回構築する CI 環境の構成図は次のとおりです。

前回の記事で構築した CI 環境からの変更点は、 Kubernetes クラスター上にコンテナレジストリのポッドが追加されることです。コンテナレジストリの IP アドレスを使用して、 Kubernetes とクラスター内のコンテナの両方から同じ URL でコンテナイメージにアクセスできます。コンテナレジストリには VM の外からもアクセス可能ですが、URL が異なる点はご注意ください。もし Docker Hub のような外部のコンテナレジストリが利用できるなら、アクセス元の場所によって URL を変更する必要はもちろんありません。
CI 環境の利用方法は、コンテナイメージのソースコードを Gitea のリポジトリにプッシュします。すると Jenkins がソースコードをチェックアウトして Kaniko を実行します。 Kaniko はコンテナイメージをビルドしてコンテナレジストリにアップロードします。アップロードされたコンテナイメージは、 Kubernetes クラスターの内外でビルド/テスト環境として利用できます。
コンテナイメージに含まれるもの
コンテナイメージにはビルド/テストに必要なコンパイラやライブラリなどを含めます。今回は C/C++ 言語の Arm アーキテクチャー向けのクロスコンパイラである Arm GNU Toolchain を利用します。余談ですが Arm の CPU は以前から組込みシステムで良く採用されていますが、現在はそれに加えて Mac や AWS の EC2 インスタンスでも採用されていますので、 Arm の CPU を使う機会がより増えたように感じます。
閑話休題。コンパイラに加えて、 C/C++ 言語のビルドツールとして良く利用される CMake, Make, Ninja もイメージに含めることにします。
Kubernetes クラスターの設定
前提として、前回の記事に沿って Kubernetes クラスターと CI 環境が構築されているものとします。 そして構成図に記載のとおりコンテナレジストリを追加します。
コンテナレジストリの追加
Kubernetes クラスターの構築には Minikube を利用しているため、コンテナレジストリの追加は Minikube の registry
プラグインを有効にすることで簡単にできます。コンテナレジストリを追加したら、その IP アドレスをメモしておきます。これらを実行する Powershell スクリプトは次のとおりです。
minikube addons enable registry
$RegistryAddress = & kubectl get service -n kube-system registry -o jsonpath='{.spec.clusterIP}'
$RegistryAddress
コンテナイメージのソースコードの作成
ビルド/テスト環境用のコンテナイメージのソースコードリポジトリを作成します。そしてその中に、コンテナイメージのビルド手順を記述する Dockerfile
ファイル、および、コンテナイメージのビルドとアップロードを行うパイプラインを定義する Jenkinsfile
ファイルを追加します。
ソースコードリポジトリの作成とクローン
gitea
コマンドを使用してリポジトリを作成します。 Powershell にて以下のスクリプトを実行します。 ${GITEA_DOMAIN}
は Gitea のドメイン名に置き換えます。
$orgName = 'exampleorg' # 組織名
$repoName = 'arm-gnu-toolchain' # リポジトリ名
$giteaUsername = 'gitea_admin' # Gitea の初期ユーザー名
$giteaPassword = 'r8sA8CPHD9!bt6d' # Gitea の初期ユーザーのパスワード
# Gitea API にアクセスするための認証情報
$headers = @{ Authorization = "Basic "+ [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes("${giteaUsername}:${giteaPassword}")) }
# リポジトリ example-pipeline を作成。 README を自動生成
$repo = Invoke-RestMethod -Method 'POST' `
-Uri "http://${GITEA_DOMAIN}/api/v1/orgs/${orgName}/repos" `
-Headers ${headers} `
-ContentType 'application/json' `
-Body (ConvertTo-Json @{ name = $repoName; auto_init = $True })
リポジトリを作成したら、リポジトリをクローンして作業ディレクトリに移動します。
git clone $repo.clone_url
cd $repoName
Dockerfile
の作成
コンテナのビルド手順を記載した Dockerfile
ファイルを作成します。ファイルの内容は以下のとおりです。 Arm GNU Toolchain のインストール手順は、 Arm GNU Toolchain のリリースノートに従います。
FROM ubuntu:23.04 AS base_image
RUN export DEBIAN_FRONTEND=noninteractive \
&& apt-get update \
&& apt-get upgrade -y \
&& apt-get install -y --no-install-recommends \
ca-certificates \
curl \
gnupg \
locales \
xz-utils \
cmake \
make \
ninja-build \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* \
&& locale-gen ja_JP.UTF-8
ENV LANG=ja_JP.UTF-8 TZ=JST-9
ARG INSTALL_DIR=/opt/arm
RUN cd /tmp \
&& curl --fail --location --remote-name \
"https://developer.arm.com/-/media/Files/downloads/gnu/12.2.mpacbti-rel1/binrel/arm-gnu-toolchain-12.2.mpacbti-rel1-x86_64-arm-none-eabi.tar.xz?rev=71e595a1f2b6457bab9242bc4a40db90&hash=41B9EE53CF6E77F7F0647767EC481951" \
&& curl --fail --location \
"https://developer.arm.com/-/media/Files/downloads/gnu/12.2.mpacbti-rel1/binrel/arm-gnu-toolchain-12.2.mpacbti-rel1-x86_64-arm-none-eabi.tar.xz.sha256asc?rev=9e9d90ff234a44aaafb1c27f0639ea56&hash=0C655496C8164E8FF86D900897681345" \
| sha256sum --check \
&& mkdir -p "${INSTALL_DIR}" \
&& tar xJf arm-gnu-toolchain-12.2.mpacbti-rel1-x86_64-arm-none-eabi.tar.xz -C "${INSTALL_DIR}" \
&& rm -f arm-gnu-toolchain-12.2.mpacbti-rel1-x86_64-arm-none-eabi.tar.xz
ENV PATH="${PATH}:${INSTALL_DIR}/arm-gnu-toolchain-12.2.mpacbti-rel1-x86_64-arm-none-eabi/bin"
Jenkinsfile
の作成
続いて CI パイプラインを定義する Jenkinsfile
ファイルを作成します。ファイルの内容は以下のとおりです。ファイル内の ${RegistryAddress}
はコンテナレジストリの IP アドレスに置き換えます。
pipeline {
agent {
kubernetes {
yaml """
kind: Pod
spec:
containers:
- name: kaniko
image: gcr.io/kaniko-project/executor:debug # Kaniko のコンテナイメージ
imagePullPolicy: Always
command:
- sleep
args:
- 9999999
"""
}
}
stages {
stage('Build with Kaniko') {
steps {
container(name: 'kaniko', shell: '/busybox/sh') {
sh '''
/kaniko/executor --context `pwd` --destination ${RegistryAddress}/exampleorg/arm-gnu-toolchain:12.2.mpacbti-rel1-x86_64-arm-none-eabi --insecure
'''
}
}
}
}
}
最後に、作成した 2 つのファイルをコミットおよびリポジトリにプッシュします。
git add .
git commit -m 'ファイルを追加'
git push
コンテナイメージのビルド
Jenkins を利用してパイプラインを実行します。まず Jenkins にログインし、 exampleorg
フォルダの行の右端にある右向きの三角アイコン(マウスカーソルを当てると Schedule a scan for exampleorg と表示されます)をクリックします。数秒後にページ左側のビルドキュー に exampleorg » arm-gnu-toolchain » main
と表示されるので、これをクリックして main
ジョブに移動します。このページでコンテナイメージのビルドの様子を確認できます。

exampleorg » arm-gnu-toolchain » main
ジョブBuild with Kaniko
ステージのログを見ると、最終的にコンテナイメージ ${RegistryAddress}/exampleorg/arm-gnu-toolchain:12.2.mpacbti-rel1-x86_64-arm-none-eabi
がプッシュされたことが分かります。

Build with Kaniko
ステージのログこれでビルド/テスト環境のコンテナイメージが用意できました!
新しいコンテナイメージでビルドしてみる
最後に、前回作成した example-pipeline
リポジトリの Jenkins パイプラインのコードを変更して、作成したコンテナイメージがきちんと利用できるかを確認してみます。まず example-pipeline
リポジトリをクローンしたディレクトリに移動します。
cd ../example-pipeline
このディレクトリに存在する Jenkinsfile
ファイルの内容を以下の内容に置き換えます。パイプラインの内容は、作成したコンテナイメージからポッドを作成して、その中でコンパイラのバージョンを出力します。ファイル内の ${RegistryAddress}
はコンテナレジストリの IP アドレスに置き換えます。
pipeline {
agent {
kubernetes {
yaml '''
apiVersion: v1
kind: Pod
spec:
containers:
- name: arm-gnu-toolchain
image: ${RegistryAddress}/exampleorg/arm-gnu-toolchain:12.2.mpacbti-rel1-x86_64-arm-none-eabi # 作成したコンテナイメージ
command:
- cat
tty: true
'''
}
}
stages {
stage('Run gcc') {
steps {
container('arm-gnu-toolchain') {
sh 'arm-none-eabi-gcc --version'
}
}
}
}
}
変更した Jenkinsfile
ファイルをコミットおよびリポジトリにプッシュします。
git add Jenkinsfile
git commit -m 'Arm GNU Toolchain に変更'
git push
変更をリポジトリにプッシュすると、 Jenkins の
パイプラインジョブの実行が開始されます。ジョブの完了後に exampleorg » example-pipeline » main
Run gcc
ステージのログを見ると、 Arm GNU Toolchain のバージョンが出力されたことが確認できます。

Run gcc
ステージのログまとめ
Kubernetes クラスター上の CI 環境で Kaniko を使用してコンテナイメージをビルドする方法をご紹介しました。これによって、ビルド/テスト環境を作成し、その環境でソフトウェアをビルド/テストする、という一連の流れをサポートする 1 つの CI 環境ができました。
なおコンテナレジストリについて、アクセスする際は認証が必要なことが多いと思います。Kubernetes上で Kaniko を使用してコンテナイメージをプッシュする際の認証情報を設定する方法については、 CloudBees CI のドキュメントを参照するのが分かりやすいと思います。