⚠️ 記事内に広告を含みます。

dockerfileでコンテナイメージをビルドする

dockerfileはdockerHubからプルしてきたベースのイメージに対してdockerfileで記述した変更・修正を加えて新しくイメージをビルドするのに用いる

dockerイメージをビルドしよう

dockerfileは端的にいえば、ベースイメージに対する変更点を記述したファイルのことです。

コンテナの作成方法として一般的なのは、

・必要最低限なLinuxだけが入ったイメージファイル

に対して
・必要なアプリケーションを追加

していくことで作成します。

元になるベースイメージに対して、追加していく設定はdockerfileに記述します。

コンテナは容量が小さいほど運用しやすく、コンテナの特性に適っています。

従って、アプリケーションを動かす土台のLinuxイメージは小さいほど運用面のメリットがあります。
また、不要なコンポーネントが少ないほど脆弱性となる標的が少なくなるのでセキュリティ上も好ましいです。

気を付けるポイントとしては

・不要なアプリケーション等がなるべく含まれない(シェルでさえバイナリ実行だけなら不要)
・必要なライブラリを含むか
・メンテされているか

例えば軽量で有名なAlpineは定番ですがmusl libcの互換性の問題があるため、googleが管理しているgcr.ioの軽量なdebianイメージなどを利用するのをお勧めします。

https://github.com/GoogleContainerTools/distroless

dockerコンテナイメージを作成する上での注意点

軽量化

dockerイメージは軽量であるほど
1.セキュリティ上好ましい(Attack surface(攻撃対象面)が減少する)
2.モビリティやデプロイの高速化
などのメリットがある。

対策としては
・軽量なベースイメージを利用する→alpineやgcr.ioのdistrolessイメージを利用
・バイナリのみを載せる
・不要なソフトウェアは入れない

buildの高速化

・Multi Stage buildを実行
・キャッシュ(apt-get updateなどの処理は最初に持ってくる

バージョンを指定する

コンテナ作成ごとに中身が変わるような設定は好ましくありません。

例えばベースイメージのバージョン指定をlatestにしているとタイミングによってバージョンが変わってしまい、最悪動かなくなるという可能性もあります。

指定可能であればバージョンは固定しておくのが良いです。

イメージの作成方法

dockerfileで作成するか、設定済みのコンテナからイメージを生成する方法があります。

IaC的な観点からはdockerfileからの生成が好ましいと思います。動いてるコンテナをパッとイメージ化したいみたいな場合はコンテナから生成しても良いでしょう。

ただし、イメージの受け渡しになるため転送もたいへんです。dockerfileはただのテキストなので可搬性が良く、イメージの機能もdockerfileを見ればわかるし、変更も加えやすいです。

dockerfileの作成

必要な機能を入れられるbaseイメージの選択を済ませたらdockerfileを記述していきます。

goのWebサーバ実行用のコンテナを作成するdockerfileを作成します。

FROM golang:1.16-alpine

# Set destination for COPY
WORKDIR /app

# Download Go modules
COPY go.mod .
COPY go.sum .
RUN go mod download

# Copy the source code. Note the slash at the end, as explained in
# https://docs.docker.com/engine/reference/builder/#copy
COPY *.go ./

# Build
RUN go build -o /docker-gs-ping

# This is for documentation purposes only.
# To actually open the port, runtime parameters
# must be supplied to the docker command.
EXPOSE 8080

# (Optional) environment variable that our dockerised
# application can make use of. The value of environment
# variables can also be set via parameters supplied
# to the docker command on the command line.
#ENV HTTP_PORT=8081

# Run
CMD [ "/docker-gs-ping" ]

Dockerfileの構成

Dockerfileの構成を詳しく見ていく

Dockerfileは複数のセクションに分かれている。上記のDockerfileには下記の項目がある。

  • FROM
  • WORKDIR
  • COPY
  • RUN
  • EXPOSE
  • CMD

これらはレイヤと呼ばれる。
FROMなどの後には命令が続く。(このレイヤの構成によってビルドの速さが変わるがここでは説明しない)

FROM

FROMはベースイメージを指定する。上の例ではgolang:1.16-alpineのイメージを利用する

FROM golang:1.16-alpine

イメージの詳細

Docker

指定してるイメージを単体でPullもできる

$ docker pull golang:1.16-alpine
$ docker run -dit --name golang-container golang:1.16-alpine
$ docker ps 
8ffc4b3fcb79   golang:1.16-alpine   "/bin/sh"   2 minutes ago  Up 2 minutes golang-container
## ↑のgolang-containerが起動している

## シェルを起動してコンテナの内部に入ることができる
$ docker exec -it golaong-container /bin/sh
# ls
/bin /src

## go コンテナ内でgoスクリプトを実行する
# cat hello.go 
package main

import "fmt"

func main() {
  fmt.Printf("Hello World\n")
}
/go/src # go run hello.go 
Hello World

/go/src # which go 
/usr/local/go/bin/go

WORKDIR

コンテナ内の作業ディレクトリを指定する。WORKDIR指定した以降の操作は指定したディレクトリで行われる。FROMで指定したイメージ内にディレクトリがなければ作成される。

WORKDIR /app

/appがなければ作成される。これ以降の作業は/appのディレクトリで行われる

COPY

COPYはDockerfileがあるディレクトリのファイルを指定した場所にコピーするコマンドです。

COPY “コピーするファイル名” “コピー先のディレクトリ”

下記のDockerfileの例を見てみましょう。

dockerfileの場所にはgo.mod, go.sumのファイルがある。 WORKDIRで/appで指定しています。go.modとgo.sumのファイルをコンテナイメージ内のカレントディレクトリ(/app)にコピーします。

WORKDIR /app

COPY go.mod .
COPY go.sum .

RUN

RUNはdockerイメージを作成する段階で実行される処理を書きます。例えば 必要なパッケージのダウンロードやアップデートの処理を書きます。

RUN go mod download
RUN go build -o /docker-gs-ping

あとで登場するCMDも似ていますが、CMDはコンテナ起動時に実行される処理を書きます。

EXPOSE

EXPOSEで指定したポートをListenできる。

EXPOSE 8080

CMD & ENTRYPINT

イメージからコンテナ生成・起動時にdocker runでコマンドを実行できます。
docker run image-name command argument

コンテナ起動時にコマンドやバイナリを複数実行したい場合に複数行書く方法

1.コマンドをまとめたシェルスクリプトを実行
2. &&でコマンドをまとめる
3.ヒアドキュメントで書く

$ docker run container-image /bin/bash << EOF
cd /var/log
pwd
ls
cat auth.log
EOF

CMDとENTRYPOINTは共にコンテナ起動時に実行される処理を書きます。
例えば、サービスの自動起動設定やアプリケーションの実行などを書きます。

Dockerfile内にはCMDまたはENTRYPOINTが1つは必要ですが、それぞれ1つだけしか書けず、複数書いた場合は最後の行だけが評価されます。
ENTRYPOINTの内容はdocker run 実行時に–entrypoint オプションで指定すれば上書きできます。

ENTRYPOINTはコンテナをバイナリ実行のみに利用したい場合に使うべきで、
CMDはENTRYPOINTで定義したコマンドのデフォルト引数を設定したい場合に用います。

CMDの設定コマンドは起動時コマンド実行のデフォルト設定→別コマンドの実行も想定(docker runで指定したコマンドが優先)
ENTRYPOINTは–entrypoint指定なければ書きかわらない→起動時実行必須のものを設定
dockerfileでCMD,ENTRYPOINTを指定しなかった場合は、ベースイメージの設定を引き継ぎます。

CMDの書式と活用

CMDコマンドの書式にはシェル形式とexec形式の2種類あります。

シェル形式を使うとPID=1がシェル(/bin/sh -c “command here”)になり、コンテナ停止のSIGTERM送信してもPID=1のシェルが受け取り、コンテナが終了しないことがあるため、exec形式が推奨されています。

##---シェル形式の記法
CMD echo "test cmd"   # /bin/sh -c 'echo test cmd' が動く

##---exec形式の記法
CMD ["echo", "test cmd"]

$HOMEなどの環境変数を展開してechoしたい場合はシェル形式で実行します。exec形式では変数展開されません。

exec形式ではCMDがENTRYPOINTの引数になる場合がある

exec形式でENTRYPOINTが存在する状態でexec形式のCMDを設定するとCMDの内容がENTRYPOINTの引数になります。

ENTRYPOINT ["ls", "-a"]
CMD ["-l", "/var/log"]
## CMDの-lはコマンドではなく、ENTRYPOINTの引数になる ls -a -l /var/log

## さらにdocker runの引数も追加できるが、CMD変数は上書きされる 
$ docker run image_name /etc/   ## → ls -a /etc/

CMDには用途や目的によって変動する引数(/var/log以外も見たければ変更する)を指定することで使い勝手が良くなります。

ENTRYPOINTがシェル形式の場合、CMDは無視されます。

ENTRYPOINTの書式

CMDと同じくシェル形式とexec形式がありますが、exec形式が推奨です。

イメージの作成

dockerfileができたらこのファイルを元にイメージを作成します。

イメージ作成はdocekr buildコマンドを使います。

$ docker build -t goimage ./

-tでイメージのタグを指定します。
./ はDockerfileのディレクトリを指定します。

イメージの作成を確認

$ docker images | grep goimage
goimage                             latest        da68c087c427   17 minutes ago   534MB

ビルド時のキャッシュ

dockerfileの項目を実行する際にdockerはキャッシュをして、再度ビルド時に同じ内容であればキャッシュを利用するのでビルド時間が短縮されます。

dockerfileの命令内容が変わらないとキャッシュが使われてしまうため、これを防ぐためには、–no-cacheオプションを利用します。

イメージからコンテナを起動

イメージからコンテナを作成・起動するにはdocker runを使用します。

$ docker run --publish 8080:8080 goimage
114019d4048ab8f853bac612949db944fe73f90b02a706e80f3b72c434f0e0b3
$ docker ps
CONTAINER ID   IMAGE     COMMAND             CREATED         STATUS         PORTS      NAMES
114019d4048a   goimage   "/docker-gs-ping"   3 seconds ago   Up 2 seconds   8080/tcp   relaxed_ritchie

http://127.0.0.1:8080にアクセスすると文字が表示されます。
軌道だけなdocker run goimageだけで良いですが、コンテナに外部からアクセスするには–pubishする必要があります。

(補足) golangの公式イメージを利用してコンテナを作成する

go langのDocker Official Imageが提供されているのでこれを使ってコンテナを作成します。

golang – Official Image | Docker Hub

コンテナの挙動を確認するため、golangで簡単なWebサーバを作成します。

コンテナ作成の流れ

  1. dockerfileを作成
  2. コンテナを作成

参考にするチュートリアルは下記

What will you learn in this module?

webサーバ作成の手順は下記を参考

Build your Go image

リポジトリ

https://github.com/olliefr/docker-gs-ping

適当なディレクトリを作成・移動してリポジトリをクローンする

$ mkdir develop/golang && cd develop/golang && pwd
$ git clone <https://github.com/olliefr/docker-gs-ping>
$ cd docker-gs-ping && ls
Dockerfile             LICENSE                go.mod                 main.go
Dockerfile.multistage  README.md              go.sum                 main_test.go

まずはmain.goをローカル環境で直接実行する

・main.goの内容

package main

import (
	"net/http"
	"os"

	"github.com/labstack/echo/v4"
	"github.com/labstack/echo/v4/middleware"
)

func main() {

	e := echo.New()

	e.Use(middleware.Logger())
	e.Use(middleware.Recover())

	e.GET("/", func(c echo.Context) error {
		return c.HTML(http.StatusOK, "Hello, Docker! <3")
	})

	e.GET("/ping", func(c echo.Context) error {
		return c.JSON(http.StatusOK, struct{ Status string }{Status: "OK"})
	})

	httpPort := os.Getenv("HTTP_PORT")
	if httpPort == "" {
		httpPort = "8080"
	}

	e.Logger.Fatal(e.Start(":" + httpPort))
}

このmain.goをコンテナを使用せずに実行する(goの実行環境が必要)

$ go run main.go
## httpPort = 8080と設定しているので、ローカルホストの8080にアクセスすると"Hello, Docker! <3"”と表示される
## URL* 127.0.0.1:8080にアクセスするかcurlして確かめる
$ curl -iL 127.0.0.1:8080
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Date: Sat, 07 Jan 2023 13:14:09 GMT
Content-Length: 17

Hello, Docker! <3

このmain.goをコンテナ内で実行する環境を作成するためにdockerfileで作成する(内容は前内容と同じ)

公式チュートリアルの続き

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です