goのアプリをDockerで開発するチュートリアルで学ぶ
オフィシャル提供のチュートリアルでDockerの動かし方を学びます
Dockerfileの書式は以下を確認してください
dockerfileでコンテナイメージをビルドするgoのWebサーバを作成していきます。
参考のチュートリアルは下記です。
What will you learn in this module?
webサーバ作成の手順は下記を参考
リポジトリ
https://github.com/olliefr/docker-gs-ping
1. dockerfileからイメージを作成
適当なディレクトリを作成・移動してリポジトリをクローンする
$ 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.g
Dockerfileからイメージをビルドする
dockerfileがあるディレクトリでビルドを実行します。
$ docker build --tag docker-gs-ping ./
###---docker-gs-pingというイメージが作成されています。
$ docker image ls | grep gs-ping
docker-gs-ping latest 1c2a2efabe2f 3 hours ago 534MB
(応用)マルチステージビルドを実行する
Dockerfileの作業は意味的に2つに分けることができます。
- main.goをビルドしてバイナリを生成する
- バイナリ実行ファイルをコンテナイメージに設置する
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のRUN go build -o /docker-gs-ping
までがバイナリ実行ファイルの生成作業です。1.ビルドに必要なファイル(go.modm go.sum, main.go)をベースイメージ内の/appにコピー
2. ビルドしてコンテナのルートディレクトリにdocker-gs-pingを吐き出し
次のバイナリ設置について、ビルドの結果バイナリが生成するので同時に行っています。
このコンテナの作成・実行時には作成したバイナリ(docker-gs-ping)を実行するようにCMDで書かれています。
何が言いたいか?というと、ビルドとデプロイを分けようということです。
- goのバイナリを生成する(ビルド)
- バイナリを実行コンテナに設置する(デプロイ)
goのビルドには高機能なベースイメージのコンテナ、実行用コンテナは最小のコンテナに設置すればイメージのサイズの節約が可能です。
プログラムのビルドにはある程度の機能が必要
goのビルドにはある程度機能が充実した=容量の大きい ベースイメージが必要です。
バイナリの実行には最小限のベースイメージでOK
高機能なOSの機能・ライブラリ等、あるいはsshdなどのサーバ機能、各種コマンドはバイナリの実行には不要です。したがって、非常に軽量なコンテナイメージが利用できます。
マルチイメージビルド用のdockerfile
マルチステージビルドとは一つのコンテナベースイメージで完結せずに、複数のイメージを利用してdcokerコンテナイメージをビルドすることです。
上記の通り、goビルドと実行用コンテナのベースイメージを分けます。
multi-stage用のdockerfile
下記のdockerfileにはFROMが2つあるのがわかります。
最初のFROMはgoビルド用のイメージgolang:1.16-buster AS buildを利用し、2つ目のFROMでは軽量で有名なdistrolessのbase-debian10イメージを利用しています。
###--- Build ---###
FROM golang:1.16-buster AS build
WORKDIR /app
COPY go.mod .
COPY go.sum .
RUN go mod download
COPY *.go ./
RUN go build -o /docker-gs-ping
###--- Deploy ---###
FROM gcr.io/distroless/base-debian10
WORKDIR /
COPY --from=build /docker-gs-ping /docker-gs-ping
EXPOSE 8080
USER nonroot:nonroot
ENTRYPOINT ["/docker-gs-ping"]
ビルドする
$ docker build -t docker-gs-ping:multistage -f Dockerfile.multistage ./
マルチステージビルドの詳細
上の例では、FROM golang:1.16-buster AS buildとあるように、最初のコンテナイメージの名前をbuildと設定し、ビルドしてdocker-ps-pingを生成しています。
2つ目のbase-debian10では最初のコンテナイメージbuildのdocker-gs-pingをbase-debian10にコピーしています。
マルチステージビルドの結果、イメージサイズを約1/20以下にまで縮小することができました。
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
docker-gs-ping multistage e908e1e06f43 3 minutes ago 23.6MB
docker-gs-ping latest 1c2a2efabe2f 3 hours ago 534MB
マルチステージビルドについては下記のURLを参考にしてください
https://docs.docker.com/build/building/multi-stage/
マルチステージビルドがない時代?はビルド用とデプロイ用のdockerfileを2つ作成し、ホストとバインドマウントしてビルド用コンテナで作成したバイナリをホストと共有し、デプロイ用コンテナでそのバイナリをコピーしてデプロイするというような方法で実施する必要があります。
マルチステージビルドは一つのdockerfileで良いので効率的です。
作成したイメージからコンテナを作成して実行する
$ docker run -d -p 8080:8080 --name rest-server docker-gs-ping
## curlでWebサーバからの応答を確認する
$ curl -iL http://localhost:8080
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Date: Sat, 14 Jan 2023 14:33:26 GMT
Content-Length: 17
Hello, Docker! <3
DBと連携する
postgreSQLと互換性のあるCockroachDBと連携します。
DBは重要な情報を保持するので、データを永続化させるためのvolumeが必要です。
WebとDBの2つのコンテナがセットになるので、docker-composeを利用します。
docker-composeの作成
DB連携するためmain.goに変更が入ります。
別のディレクトリで以下のレポジトリをクローンしてください
$ git clone https://github.com/olliefr/docker-gs-ping-roach.git
version: '3.8'
services:
docker-gs-ping-roach:
depends_on:
- roach
build:
context: .
container_name: rest-server
hostname: rest-server
networks:
- mynet
ports:
- 80:8080
environment:
- PGUSER=${PGUSER:-totoro}
- PGPASSWORD=${PGPASSWORD:?database password not set}
- PGHOST=${PGHOST:-db}
- PGPORT=${PGPORT:-26257}
- PGDATABASE=${PGDATABASE:-mydb}
deploy:
restart_policy:
condition: on-failure
roach:
image: cockroachdb/cockroach:latest-v20.1
container_name: roach
hostname: db
networks:
- mynet
ports:
- 26257:26257
- 8080:8080
volumes:
- roach:/cockroach/cockroach-data
command: start-single-node --insecure
volumes:
roach:
networks:
mynet:
driver: bridge
DBパスワード用の.envファイルを作成
.envファイル
PGPASSWORD=whatever
docker-composeで起動
$ docker-compose config
→設定の確認とエラーチェック
$ docker-compose up -d --build
→ イメージをビルドして起動する。後にdocker-composeファイルを変更した場合には、--buildを入れないとイメージの再構築が行われずイメージに反映されないので注意
DB設定
DBコンテナに入ってデータベースとユーザを作成します。
$ docker exec -it roach ./cockroach sql --insecure
oot@:26257/defaultdb> show databases;
database_name
-----------------
defaultdb
postgres
system
(3 rows)
Time: 34.377ms
root@:26257/defaultdb> CREATE DATABASE mydb;
CREATE DATABASE
Time: 41.215ms
root@:26257/defaultdb> show databases;
database_name
-----------------
defaultdb
mydb
postgres
system
(4 rows)
Time: 27.289ms
root@:26257/defaultdb> CREATE USER totoro;
CREATE ROLE
Time: 44.194ms
root@:26257/defaultdb> GRANT ALL ON DATABASE mydb TO totoro;
GRANT
Time: 41.578ms
アプリケーションの動作確認
ローカルホスト:80にアクセスして動作を確認します。
$ curl http://localhost
Hello, Docker! (0)
##POSTでデータを送るとユニークなバリューが増えると()の中の数字が増加する
$ curl --request POST \
> --url http://localhost/send \
> --header 'content-type: application/json' \
> --data '{"value": "Hello, Docker!"}'
{"value":"Hello, Docker!"}
$ curl http://localhost/
Hello, Docker! (1)
$ curl --request POST --url http://localhost/send --header 'content-type: application/json' --data '{"value": "Hello, Docker"}'
{"value":"Hello, Docker"}
$ curl http://localhost/
Hello, Docker! (2)
$ curl --request POST --url http://localhost/send --header 'content-type: application/json' --data '{"value": "WWWWWWWWOOOOOOOW"}'
{"value":"WWWWWWWWOOOOOOOW"}
$ curl http://localhost/
Hello, Docker! (3)
$ curl --request POST --url http://localhost/send --header 'content-type: application/json' --data '{"value": "WWWWWWWWOOOOOOOW"}'
{"value":"WWWWWWWWOOOOOOOW"}
$ curl http://localhost/
Hello, Docker! (3)
docker volumeの作成
roachという名前のボリュームを作成します。
volumes:
roach:
$ docker volume create roach
roach
$ docker volume list
DRIVER VOLUME NAME
local roach
作成したroachボリュームをDBコンテナの/cockroach/cockroach-dataディレクトリにマウントします。
volumes:
- roach:/cockroach/cockroach-data
ネットワークの作成
WebサーバとDBサーバが互いに通信できるようにネットワークを作成します。
mynetというブリッジネットワークを新規作成します。
networks:
mynet:
driver: bridge
$ docker network create -d bridge mynet
b34d47e8bcf6b26117281b1696972b2c4e58a1e196f8df22424cafbee770659f
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
4dcc1f533e83 bridge bridge local
763de16ea37b host host local
b34d47e8bcf6 mynet bridge local
82071b8789b8 none null local
web(docker-gs-ping-roach)とDB(roach)共にmynetネットワークを利用する。ついでにポートも設定。
services:
docker-gs-ping-roach:
networks:
- mynet
ports:
- 80:8080
roach:
networks:
- mynet
ports:
- 26257:26257
- 8080:8080
残りの部分
depends_onで設定したroachが起動してからWebサーバ側を起動する
depends_on:
- roach
contextの設定
ビルドのcontextの設定はdocker-compose.ymlと同じカレントディレクトリを指定してる。ビルドに用いるdockerfileの場所が同じディレクトリにあるので.で指定。
build:
context: .
## 別ディレクトリにあるならdockerfile: ./setting/Dockerfile
コンテナ終了した時の再起動オプション。conditionで再起動する条件を設定。on-failureで終了コードが0以外のエラー時に再起動する。
deploy:
restart_policy:
condition: on-failure
cockroach DBのTLS・認証をスキップするコマンドをDBコンテナで実行する
command: start-single-node --insecure
services:
docker-gs-ping-roach:
depends_on:
- roach
build:
context: .
container_name: rest-server
hostname: rest-server
networks:
- mynet
ports:
- 80:8080
environment:
- PGUSER=${PGUSER:-totoro}
- PGPASSWORD=${PGPASSWORD:?database password not set}
- PGHOST=${PGHOST:-db}
- PGPORT=${PGPORT:-26257}
- PGDATABASE=${PGDATABASE:-mydb}
deploy:
restart_policy:
condition: on-failure
roach:
image: cockroachdb/cockroach:latest-v20.1
container_name: roach
hostname: db
networks:
- mynet
ports:
- 26257:26257
- 8080:8080
volumes:
- roach:/cockroach/cockroach-data
command: start-single-node --insecure
CI/CDのテスト
docker hubのアカウント登録(docker IDの取得)
Docker Hub Container Image Library | App Containerization
サインアップ時のユーザ名がdocker IDになります。
Github
- リポジトリを作成する
- リポジトリのページからsetting, secres and variables→ Actionsを選択
- New Repository secretにName:DOCKERHUB_USERNAME, SECRETにdocker IDを入力して作成
- アクセストークンを発行する
- https://hub.docker.com/settings/security
- clockboxciというdiscriptionを入れる
- githubでDOCKERHUB_TOKENという名前のシークレットを作成、値にアクセストークンを入力する
ワークフロー設定
- GithubのレポジトリページでActionsに移動
- set up a a workdlow yourselfを選択
- 下記の内容をmain.ymlに入力
ワークフローの手順を定義
メインブランチにプッシュされた際にDocker Setup Buildx の機能を使ってコンテナイメージを構築して、docker hubにイメージをプッシュする
name: ci
on:
push:
branches:
- "main"
jobs:
build:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v3
-
name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
-
name: Build and push
uses: docker/build-push-action@v3
with:
context: .
file: ./Dockerfile
push: true
tags: ${{ secrets.DOCKERHUB_USERNAME }}/clockbox:latest
ワークフローの開始
- main.ymlをStart Commitして、mainブランチにプッシュする
- Actionタブに移動してコミットが正常に終了するか確認
- Docker hubに移動
- docekr hubにgithubのリポジトリで新しいイメージがプッシュされていれば完了。