0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

S2I (Source to Image)解説 - 3. S2Iの機能、S2I 増分ビルド (S2I incremental build)を試してみる

Last updated at Posted at 2023-10-01

S2I増分ビルド (incremental build)とはS2Iの機能の一つであり,前回のビルドとの差分を読み取ることで,ビルドプロセスを短縮する機能である.ここではgo言語を用いて,S2I 増分ビルドを実行する.

S2I build workflow

まず,S2Iのビルドワークフローについて確認する.S2I buildを実行すると,次のようなワークフローに従って,コンテナイメージがビルドされる.:
sti-flow.png
この記事では,図の赤い四角で囲われたS2I増分ビルドプロセスについて,詳しく解説を行う.

golangを例にしたS2I増分ビルドの実行

まず,S2I 増分ビルドを行うまでの手順をコマンドを示して解説する.その後,S2I buildを実行した後にどのようなメカニズムで増分ビルドのワークフローが実行されるかを図示しながら説明していく.また,S2I増分ビルドを実行するためのS2I scriptについては,次節で説明する.

S2I増分ビルドを実行するまで

まず,s2i builder imageの作成を行う.前回pythonの環境用のコンテナイメージを構築したように,コンテナイメージのビルドを行う.

  1. まず,コンテナイメージを作成するためのディレクトリ s2i-golang-incrementalを作成し,以下のように用意する:
s2i-golang-incremental/
├── builder
│   ├── Dockerfile
│   └── s2i
│       └── bin
│           ├── assemble
│           ├── run
│           ├── save-artifacts
│           └── usage
└── test-app
    ├── app.go
    ├── go.mod
    └── go.sum

ここで,ディレクトリ buildertest-appはそれぞれS2I builder imageをビルドする用,ソースコード用のディレクトリである.builderにはDockerfileS2I 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"]
assemble.sh
#!/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
run.sh
${APP_ROOT}/bin/app
save-artifacts.sh
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のサンプルファイルが置いてある.ソースコードを以下に示す:

main.go
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 scriptDockerfileを用いて,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のライブラリ部分を書き加え,

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を実行すると,以下のプロセスが実行される:

  1. S2Iがローカルに存在する前回作成されたコンテナイメージ (docker imagesで見ることができるコンテナイメージ)を探索

  2. S2Iが前回ビルドしたイメージからコンテナを立ち上げる

  3. 立ち上げたコンテナ内のライブラリ (それぞれの言語で作られたバイナリのこと) を抽出し, tar archiveにまとめ、標準出力に流す

  4. 標準入力として新しいコンテナイメージがtar archiveを受け取り、S2I がtarを展開する

  5. そして,assembleが実行され,新しいコンテナイメージがビルドされる

S2I-incremental.png

s2i-scriptsの解説

ここでは,S2I scriptに記述された内容について,詳細を説明する.

assemble script

assemble.sh
#!/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 dotglobdotglobオプションを有効にする。
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 dotglobdotglobオプションを無効にする。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増分ビルドを行うためには,必須のスクリプトになる.このスクリプトは増分前の情報をキャッシュしておく役目をする.スクリプトの中身を詳しく見ていく.

:save-artifacts.sh
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に操作を行う.また,オプション+wsrcへ書き込み権限を追加する.
  • tar : ファイルやディレクトリをアーカイブ(まとめてひとつのファイルにすること)するために使用される.
    ここではsrcディレクトリをtarアーカイブにパッキングし、そのアーカイブを標準出力に書き出す操作をしている。ここで,cfは、アーカイブを作成するためのオプションであり、-はアーカイブを標準出力に書き出すことを意味する。

S2i増分ビルド中のログの確認

ログレベルオプション

s2i buildコマンドでは,--lolevelというオプションを使うことでビルドプロセスのログを詳しくみることができる.コマンドは以下のように実行すればよい.

$ s2i < 他のオプション >  --loglevel=5 &> /tmp/s2i.log

ログレベルはレベル0から5まである:

  • Level 0 - assembleassemble-runtimeスクリプトを実行しているコンテナからの出力と遭遇したエラーすべてを表示する.
  • Level 1 - 実行されたプロセスに関して標準的な情報を表示する
  • Level 2 - 実行されたプロセスに関して非常に詳細な情報を表示する.
  • Level 3 - 実行されたプロセスに関して非常に詳細な情報を、また,tar の内容も表示する。
  • Level 4 - 現在はlevel 3と同じ情報が表示される.
  • 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
  1. まず、イメージmy-go-appがローカルに存在しないので、それをpullしようとする。
  2. このとき、イメージmy-go-appが存在しないため、pulling image errorが発生した。
  3. そのため、s2iがイメージmy-go-appをpullできないと判断する。
  4. 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/
  1. ローカルに存在するimage my-go-app:latestの使用を宣言
  2. incremental buildを行うための使用可能なイメージmy-go-app:latestがローカルに存在することを特定。
  3. 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に関する解説記事は終わりです。

  1. ここで,再帰的とは,dir1/dir2/dir3/・・・などのように階層型になっているディレクトリに対して,dir1に対して,dir1/dir2に対して,・・・とまとめて操作を行うことを意味する.

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?