S2I増分ビルド (incremental build)とはS2Iの機能の一つであり,前回のビルドとの差分を読み取ることで,ビルドプロセスを短縮する機能である.ここではgo言語を用いて,S2I 増分ビルドを実行する.
S2I build workflow
まず,S2Iのビルドワークフローについて確認する.S2I buildを実行すると,次のようなワークフローに従って,コンテナイメージがビルドされる.:
この記事では,図の赤い四角で囲われたS2I増分ビルドプロセスについて,詳しく解説を行う.
golangを例にしたS2I増分ビルドの実行
まず,S2I 増分ビルドを行うまでの手順をコマンドを示して解説する.その後,S2I build
を実行した後にどのようなメカニズムで増分ビルドのワークフローが実行されるかを図示しながら説明していく.また,S2I増分ビルドを実行するためのS2I scriptについては,次節で説明する.
S2I増分ビルドを実行するまで
まず,s2i builder imageの作成を行う.前回pythonの環境用のコンテナイメージを構築したように,コンテナイメージのビルドを行う.
- まず,コンテナイメージを作成するためのディレクトリ
s2i-golang-incremental
を作成し,以下のように用意する:
s2i-golang-incremental/
├── builder
│ ├── Dockerfile
│ └── s2i
│ └── bin
│ ├── assemble
│ ├── run
│ ├── save-artifacts
│ └── usage
└── test-app
├── app.go
├── go.mod
└── go.sum
ここで,ディレクトリ builder
とtest-app
はそれぞれS2I builder imageをビルドする用,ソースコード用のディレクトリである.builder
にはDockerfile
とS2I script
が置いてある.これらのソースコードを以下に示す:
# ベースイメージ
FROM openshift/base-centos7
# ラベルを指定
LABEL io.openshift.s2i.scripts-url="image:///usr/libexec/s2i/bin"
# Goのバージョンとインストール先
ARG GO_VERSION=1.19
ARG GO_INSTALL_DIR=/usr/local/
# 環境変数
ENV APP_ROOT=/opt/app-root
ENV BUILDER_VERSION 1.0
ENV GOPATH ${APP_ROOT}/src/go
ENV PATH=${PATH}:${GOPATH}/bin:${GO_INSTALL_DIR}/go/bin
# Goのインストール
RUN curl -sSL https://dl.google.com/go/go${GO_VERSION}.linux-amd64.tar.gz -o go${GO_VERSION}.linux-amd64.tar.gz && \
tar -C ${GO_INSTALL_DIR} -xzf go${GO_VERSION}.linux-amd64.tar.gz && \
rm -f go${GO_VERSION}.linux-amd64.tar.gz && \
mkdir -p ${GOPATH}
# s2i scriptsをコンテナへコピー
COPY s2i /usr/libexec/s2i
# パーミッションを設定
RUN chown -R 1001:1001 ${APP_ROOT} && \
chown -R 1001:1001 /usr/libexec/s2i
# default user
USER 1001
# default CMD for the image
CMD ["/usr/libexec/s2i/usage"]
#!/bin/bash -e
# usageスクリプトを実行
if [[ "$1" == "-h" ]]; then
exec /usr/libexec/s2i/usage
fi
# S2I増分ビルドを行うためのスクリプト
echo
echo "---> Checking for cache..."
if [ "$(ls /tmp/artifacts 2>/dev/null)" ]; then
pushd /tmp/artifacts >/dev/null
echo "-----> Pulling cache..."
shopt -s dotglob
if [ -d src ]; then
echo "Restoring cache ${GOPATH}/src/..."
mv src ${GOPATH}/src
fi
if [ -d pkg ]; then
echo "Restoring cache ${GOPATH}/pkg/..."
mv pkg ${GOPATH}/pkg
fi
shopt -u dotglob
popd >/dev/null
fi
# Goソースファイルのビルド
echo
echo "---> Building application from source..."
pushd /tmp/src/ >/dev/null
go build -o ${APP_ROOT}/bin/app
popd >/dev/null
${APP_ROOT}/bin/app
pushd ${GOPATH} >/dev/null
if [ -d src ]; then
chmod -R +w src
tar cf - src
fi
if [ -d pkg ]; then
chmod -R +w pkg
tar cf - pkg
fi
popd >/dev/null
上に示したコードの解説は,s2i-scriptsの解説で行う.また,test-app
には,今回使用するgolangのサンプルファイルが置いてある.ソースコードを以下に示す:
package main
import (
"fmt"
_ "github.com/golang/mock/gomock"
_ "github.com/gorilla/mux"
)
func main() {
greeting := fmt.Sprintf("Hello, %s", "World")
fmt.Println(greeting)
}
2.Dockerfileとs2iスクリプトのあるディレクトリで以下を実行
現在,s2i-golang
というディレクトリにいるとする.そこで,以下のコマンドでs2i builder imageをビルドする:
$ docker build -t s2i-golang s2i-golang/builder
このコマンド実行は,s2i-golang/builder
のディレクトリ内のs2i script
とDockerfile
を用いて,s2i-golang
というs2i builder imageをビルドする.
3.1回目のs2iビルドを実行する.
まず,増分を行う前のコンテナイメージをビルドする.
$ s2i build --copy test-app/ s2i-golang my-go-app
-
--copy
: githubなどのリポジトリの代わりにローカルのディレクトリからアプリケーションソースコードをコンテナへコピーするためのオプションである.
4.goのライブラリを追加し,S2i増分ビルドを行う.
まず,以下のようにmain.go
のライブラリ部分を書き加え,
package main
import (
"fmt"
_ "github.com/golang/mock/gomock"
_ "github.com/gorilla/mux"
_ "github.com/joho/godotenv"
_ "github.com/labstack/echo/v4"
_ "github.com/sirupsen/logrus"
_ "gorm.io/driver/mysql"
)
func main() {
greeting := fmt.Sprintf("Hello, %s", "James")
fmt.Println(greeting)
}
$ go mod tidy
を実行することで,go.mod
, go.sum
が更新され,ライブラリ情報が追加される.その後,S2I incremental buildを実行する:
$ s2i build --rm --copy test-app/ s2i-golang my-go-app --incremental=true
このコマンドは,my-go-app
というコンテナイメージを増分ビルドしている.コマンドの引数は以下:
-
--rm
: 増分前のコンテナイメージを削除する -
--incremental=true
: 増分ビルドを行うためのオプションである
S2I増分ビルドのメカニズム
S2I incremental buildを実行すると,以下のプロセスが実行される:
-
S2Iがローカルに存在する前回作成されたコンテナイメージ (
docker images
で見ることができるコンテナイメージ)を探索 -
S2Iが前回ビルドしたイメージからコンテナを立ち上げる
-
立ち上げたコンテナ内のライブラリ (それぞれの言語で作られたバイナリのこと) を抽出し, tar archiveにまとめ、標準出力に流す
-
標準入力として新しいコンテナイメージがtar archiveを受け取り、S2I がtarを展開する
-
そして,assembleが実行され,新しいコンテナイメージがビルドされる
s2i-scriptsの解説
ここでは,S2I scriptに記述された内容について,詳細を説明する.
assemble script
#!/bin/bash -e
# usageスクリプトを実行
if [[ "$1" == "-h" ]]; then
exec /usr/libexec/s2i/usage
fi
# S2I増分ビルドを行うためのスクリプト
echo
echo "---> Checking for cache..."
if [ "$(ls /tmp/artifacts 2>/dev/null)" ]; then
pushd /tmp/artifacts >/dev/null
echo "-----> Pulling cache..."
shopt -s dotglob
if [ -d src ]; then
echo "Restoring cache ${GOPATH}/src/..."
mv src ${GOPATH}/src
fi
if [ -d pkg ]; then
echo "Restoring cache ${GOPATH}/pkg/..."
mv pkg ${GOPATH}/pkg
fi
shopt -u dotglob
popd >/dev/null
fi
# Goソースファイルのビルド
echo
echo "---> Building application from source..."
pushd /tmp/src/ >/dev/null
go build -o ${APP_ROOT}/bin/app
popd >/dev/null
スクリプトの中身を順にみていく.
echo "---> Checking for cache..."
if [ "$(ls /tmp/artifacts 2>/dev/null)" ]; then
pushd /tmp/artifacts >/dev/null
echo "-----> Pulling cache..."
shopt -s dotglob
/tmp/artifacts/
ディレクトリが存在するかを確認。もし存在する場合は、pushd /tmp/artifacts >/dev/null
で/tmp/artifacts/
ディレクトリに移動する。pushd
コマンドを使うと、移動先の履歴をスタックに保存したままディレクトリに移動できる。>/dev/null
は出力の破棄を行うリダイレクトである。shopt -s dotglob
でdotglob
オプションを有効にする。
shopt
コマンドはシェルのオプションを変更することができるコマンドで、-s でどのオプションを変更するかを決める.dotglob
オプションを有効にすると,ファイル名展開時に.
から始まるファイルも含めることができる.
if [ -d src ]; then
echo "Restoring cache ${GOPATH}/src/..."
mv src ${GOPATH}/src
fi
src
ファイルが存在するならば、そのとき、src
ディレクトリを${GOPATH}/src
へ移動する。
if [ -d pkg ]; then
echo "Restoring cache ${GOPATH}/pkg/..."
mv pkg ${GOPATH}/pkg
fi
shopt -u dotglob
popd >/dev/null
fi
pkg
ディレクトリが存在するならば${GOPATH}/pkg
へ移動する。shopt -u dotglob
でdotglob
オプションを無効にする。pushd
でスタックに保存したディレクトリの履歴/tmp/artifacts/
へ移動する。
echo
echo "---> Building application from source..."
pushd /tmp/src/ >/dev/null
go build -o ${APP_ROOT}/bin/app
popd >/dev/null
/tmp/src
ディレクトリに履歴をスタックにして残しながら、移動。${APP_ROOT}/bin/app
にディレクトリに対して,go build
を実行する。popd
でスタックに保存していたディレクトリ先に戻る。
save-artifactスクリプト
save-artifacts
スクリプトはS2I増分ビルドを行うためには,必須のスクリプトになる.このスクリプトは増分前の情報をキャッシュしておく役目をする.スクリプトの中身を詳しく見ていく.
pushd ${GOPATH} >/dev/null
if [ -d src ]; then
chmod -R +w src
tar cf - src
fi
if [ -d pkg ]; then
chmod -R +w pkg
tar cf - pkg
fi
popd >/dev/null
コマンドを解説する:
chmod -R +w src
tar cf - src
chmod
コマンドとtar
コマンドによる操作は
- chmod : ファイルやディレクトリのパーミッション (アクセス権限) を変更するコマンドである.
ここでは,src
ディレクトリ内のすべてのファイルとサブディレクトリへの書き込み権限を追加している。オプション-R
はディレクトリ内のすべてのファイルとサブディレクトリに対して再帰的 (Recursive)1に操作を行う.また,オプション+w
はsrc
へ書き込み権限を追加する.
- tar : ファイルやディレクトリをアーカイブ(まとめてひとつのファイルにすること)するために使用される.
ここではsrc
ディレクトリをtar
アーカイブにパッキングし、そのアーカイブを標準出力に書き出す操作をしている。ここで,cf
は、アーカイブを作成するためのオプションであり、-
はアーカイブを標準出力に書き出すことを意味する。
S2i増分ビルド中のログの確認
ログレベルオプション
s2i build
コマンドでは,--lolevel
というオプションを使うことでビルドプロセスのログを詳しくみることができる.コマンドは以下のように実行すればよい.
$ s2i < 他のオプション > --loglevel=5 &> /tmp/s2i.log
ログレベルはレベル0から5まである:
- Level
0
-assemble
とassemble-runtime
スクリプトを実行しているコンテナからの出力と遭遇したエラーすべてを表示する. - Level
1
- 実行されたプロセスに関して標準的な情報を表示する - Level
2
- 実行されたプロセスに関して非常に詳細な情報を表示する. - Level
3
- 実行されたプロセスに関して非常に詳細な情報を、また,tar の内容も表示する。 - Level
4
- 現在はlevel3
と同じ情報が表示される. - Level
5
- 実行されたプロセスに関して非常に詳細な情報,tar の内容Dockerレジストリの認証情報、コピーされたソースファイルを一覧表示する。
ログ情報の観測
ビルドプロセスのログ情報を観測することで,S2Iがどのようにビルドを実行しているかや各オプションによって何が実行されるかがわかる:
まず,増分ビルドのオプション--incremental
を実行した場合,なにが行われているかを見てみる:
まず、my-go-app
というs2iコンテナイメージ(アプリケーションイメージ)がない場合に以下のコマンドでs2iビルドを実行する:
$ s2i build --rm --copy test-app/ s2i-golang my-go-app --incremental=true
--incremental
は次のような
すると、以下のようなログが得られる:
docker.go:487] Image "my-go-app:latest" not available locally, pulling ...
docker.go:566] pulling image error : Error response from daemon: pull access denied for my-go-app, repository does not exist or may require 'docker login': denied: requested access to the resource is denied
sti.go:497] Unable to pull previously built image "my-go-app": unable to get my-go-app:latest
sti.go:215] Clean build will be performed
- まず、イメージ
my-go-app
がローカルに存在しないので、それをpullしようとする。 - このとき、イメージ
my-go-app
が存在しないため、pulling image errorが発生した。 - そのため、s2iがイメージ
my-go-app
をpullできないと判断する。 - cleanな新しいビルドが実行される。
つまり、参照するためのイメージが存在しない場合、普通のs2iビルドが実行されることがわかる。
次に、コンテナイメージmy-go-app
が存在する場合にs2i incremental buildを実行する:
$ s2i build --rm --copy test-app/ s2i-golang my-go-app --incremental=true
このとき、以下のようなログを得る:
docker.go:491] Using locally available image "my-go-app:latest"
sti.go:213] Existing image for tag my-go-app detected for incremental build
sti.go:218] Performing source build from test-app/
- ローカルに存在するimage
my-go-app:latest
の使用を宣言 - incremental buildを行うための使用可能なイメージ
my-go-app:latest
がローカルに存在することを特定。 -
test-app/
からソースのビルドを実行する
sti.go:524] Saving build artifacts from image my-go-app to path /tmp/s2i2961405759/upload/artifacts
- イメージ
my-go-app
内のpath/tmp/s2i2961405759/upload/artifacts
からbuild artifactsを保存する。
次に、artifactsを抽出するために、user1001でassembleを使用。s2iが前回ビルドしたイメージからコンテナを立ち上げ、artifactsからパッケージを抽出し、tarアーカイブにする。tarアーカイブを標準出力に流し込み、新しいビルドコンテナが標準入力として受け取り、展開する。
sti.go:550] Using assemble user "1001" to extract artifacts
docker.go:731] Image sha256:c3a02d781... contains io.openshift.s2i.scripts-url set to "image:///usr/libexec/s2i/bin"
docker.go:805] Base directory for S2I scripts is '/usr/libexec/s2i/bin'. Untarring destination is '/tmp'.
docker.go:971] Creating container with options {Name:"s2i_my_go_app_f4cb4822" Config:{Hostname: Domainname: User:1001 ...
docker.go:1003] Attaching to container ...
docker.go:1014] Starting container ...
この後はひたすら、creatingとextractingを繰り返し,ライブラリに関する情報をtar
に集めていく:
tar.go:391] Creating directory /tmp/s2i3409979752/upload/artifacts/pkg
tar.go:391] Creating directory /tmp/s2i3409979752/upload/artifacts/pkg/mod
tar.go:391] Creating directory /tmp/s2i3409979752/upload/artifacts/pkg/mod/github.com
tar.go:391] Creating directory /tmp/s2i3409979752/upload/artifacts/pkg/mod/github.com/golang
tar.go:391] Creating directory /tmp/s2i3409979752/upload/artifacts/pkg/mod/github.com/golang/mock@v1.6.0
tar.go:391] Creating directory /tmp/s2i3409979752/upload/artifacts/pkg/mod/github.com/golang/mock@v1.6.0/ci
tar.go:400] Creating directory /tmp/s2i3409979752/upload/artifacts/pkg/mod/github.com/golang/mock@v1.6.0/ci
......
......
......
tar.go:424] Done extracting tar stream
そして,S2Iが増分前のビルドでダウンロードしたライブラリについては,キャッシュを使う.追加で加えたライブラリについては,新たにダウンロードしていることがわかる:
- 増分前のログ
sti.go:713] ---> Checking for cache...
sti.go:713]
sti.go:713] ---> Building application from source...
sti.go:717] go: downloading github.com/golang/mock v1.6.0
sti.go:717] go: downloading github.com/gorilla/mux v1.8.0
docker.go:1070] Invoking PostExecute function
- 増分後のログ
sti.go:713] ---> Checking for cache...
sti.go:713] -----> Pulling cache...
sti.go:713] Restoring cache /opt/app-root/src/go/pkg/...
sti.go:713]
sti.go:713] ---> Building application from source...
sti.go:717] go: downloading github.com/joho/godotenv v1.5.1
.....
.....
.....
sti.go:717] go: downloading golang.org/x/text v0.11.0
docker.go:1070] Invoking PostExecute function
今回で,S2Iに関する解説記事は終わりです。
-
ここで,再帰的とは,
dir1/dir2/dir3/・・・
などのように階層型になっているディレクトリに対して,dir1
に対して,dir1/dir2
に対して,・・・とまとめて操作を行うことを意味する. ↩