[Docker] goのイメージ作成

main.go

package main

import(
	"net/http"
	"fmt"
)

func main(){
	http.HandleFunc("/",
		func(w http.ResponseWriter, r *http.Request){
			fmt.Fprintf(w, "hello go")
		})
	http.ListenAndServe(":80", nil)
}

Dockerfile

From golang:1.13.4-alpine3.10
WORKDIR /src
COPY ./main.go /src
RUN go build -o /usr/local/bin/startapp main.go
WORKDIR /
CMD ["/usr/local/bin/startapp"]

$ sudo docker image build ./ -t c4app3
$ sudo docker run –rm -d -p 8080:80 –name myapp c4app3

1. イメージ作成に必要な作業を別のコンテナで実施
2. 作業用コンテナで作成したイメージから本番ようイメージで使うデータを持ってくる

as builderとして1つ目の作業イメージに名前を与え、2つ目のイメージ後半で–from=builderとしてバイナリファイルをイメージ内部にコピー

From golang:1.13.4-alpine3.10 as builder
WORKDIR /src
COPY ./main.go /src
RUN go build -o start_appserver main.go

From alpine:3.10.3
COPY --from=builder /src/start_appserver /bin/start_appserver
CMD ["/bin/start_appserver"]

OSを搭載しないイメージ(Scratchと呼ぶ)
こうも書ける

From golang:1.13.4-alpine3.10 as builder
WORKDIR /src
COPY ./main.go /src
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a installsuffix cgo -o startapp main.go

From scratch
COPY --from=builder /src/startapp /startapp
CMD ["/startapp"]

自分が手動で展開できないものはDockerfile上の手順に落とし込むことはできない
– スクリプト構成を理解
– 公式を展開
– 機能を追加 
などを手を動かしてやる

差分イメージは減らす

RUN yum update \
	&& yum install -y \
		package-bar \
		package-baz \
		package-foo \
	&& rm -rf /var/cache/yum/* \
	&& yum clean all

[Docker] コンテナでPostfixを立てたい

$ tree
.
├── Dockerfile
├── configs
│   ├── main.cf
│   └── sasl_passwd
├── docker-compose.yml
└── entrypoint.sh

docker-compose.yml

version: '3.8'
services:
  docker-ubuntu-postfix-example:
    build:
      context: ./
      dockerfile: dockerfile
    image: docker-ubuntu-postfix-example-image: lastes
    container_name: docker-ubuntu-postfix-example-container
    volumes:
      # Postfixの設定をマウント
      - type: bind
        source: ./configs/main.cf
        target: /etc/postfix/main.cf
      # SASL認証のパスワードをマウント
      - type: bind
        source: ./configs/sasl_passwd
        target: /etc/postfix/sasl_passwd

Dockerfile

FROM ubuntu:20.04

RUN apt update && apt upgrade -y

# postfix install
RUN DEBIAN_FRONTEND=noninteractive apt install postfix -y

# SMTPにはSMTP AUTHが必要
# SMTP AUTHの為のSASLにはCyrus SaslとCyrus IAMPを使う
RUN apt install sasl2-bin -y
RUN DEBIAN_FRONTEND=noninteractive apt install cyrus-imapd -y

# コンテナ起動のスクリプト
COPY ./entrypoint.sh /
ENTRYPOINT ["/entrypoint.sh"]

entrypoint.sh

#!/bin/bash

# postfix起動
postfix start

# Postfixは/var/spool/postfixにchrootするので、
# /etc/resolv.confではなく/var/spool/postfix/etc/resolv.confを見に行く。

cp /etc/resolv.conf /var/spool/postfix/etc/resolv.conf

# SASL認証用テーブル作成
chown root:root /tec/postfix/sasl_passwd
postmap /etc/postfix/sasl_passwd

# postfixの設定を反映させる
postfix reload

# コンテナの起動を維持
tail -f /dev/null

entrypoint.sh

#!/bin/bash

# postfix起動
postfix start

# Postfixは/var/spool/postfixにchrootするので、
# /etc/resolv.confではなく/var/spool/postfix/etc/resolv.confを見に行く。

cp /etc/resolv.conf /var/spool/postfix/etc/resolv.conf

# SASL認証用テーブル作成
chown root:root /tec/postfix/sasl_passwd
postmap /etc/postfix/sasl_passwd

# postfixの設定を反映させる
postfix reload

# コンテナの起動を維持
tail -f /dev/null

main.cf

# ログの出力設定
mainlog_file = /var/log/mail.log

# SMTPリレーの設定
relayhost = 
smtp_sasl_auth_enable = 
smtp_sasl_mechanism_filter = 
smtp_sasl_security_options = 
smtp_sasl_password_maps = 

なるほどー、結構考えないといけないな…

[Docker] Dockerfileと.dockerignore

Dockerfileはイメージを作成するための手順書(設計書)

### 開発フロー
1. 既存の開発環境を整える
2. 開発環境上でコードを書いて動作検証
3. 成果物ができたらリリース準備
4. 新規に本番環境をセットアップするか、既存の本番環境に変更を加える
5. 開発成果物を本番環境に移行し動作させる
6. 機能更新やアップデートが必要となり1に戻る

server.py

import os, flask
PORT = int(os.environ['PORT'])
app = flask.Flask('app server')
@app.route('/')
def index():
	return "hello Dockerfile"
app.run(debug=True, host='0.0.0.0', port=PORT)

Dockerfile

From python:3.7.5-slim
Label author="myname@example.com"
RUN pip install flask==1.1.1
COPY ./server.py /server.py
ENV PORT 80
CMD ["python", "-u", "/server.py"]

$ sudo docker image build ./ -t c4app1
$ sudo docker run –rm -d -p 8080:80 –name myapp c4app1

Dockerfileの内容がキャッシュされるので、頻繁に変更が発生する箇所はDockerfileの後半に持ってくる

### ディレクトリの
.dockerignoreを配置しておくと、DockerfileのCOPY命令などの対象から指定した形式のファイルは除外される

.dockerignore

**/__pycache__
**/.DS_Store
**/Thumbs.db

Dockerfile.local.yml

From centos:7.7.1908
RUN rpm -ivh http://nginx.org/package/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm
RUN yum -y install nginx
VOLUME /volume
EXPOSE 80
USER nginx
COPY ./html/ /usr/share/nginx/html/
USER root
ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]

expose: ポートの宣言
USER: ユーザの変更
ENTRYPOINT: イメージのデフォルト実行コマンドを定義

$ sudo docker image build -f Dockerfile.local.yml -t mynginx ./

なんやー ここにきて先が見えなくなってきた

[Docker] WordPress

$ sudo docker network create -d bridge wp-net
$ sudo docker run -d –network wp-net –name mysql \
–mount source=mysqlvolume,target=/var/lib/mysql \
–mount type=bind,source=/home/vagrant/dev/docker/book,target=/mysqlbackup \
-e MYSQL_ROOT_PASSWORD=password -e MYSQL_DATABASE=wordpress \
-e MYSQL_USER=wordpress -e MYSQL_ROOT_PASSWORD=password mysql:5.7.28
$ sudo docker run -d –network wp-net -p 8080:80 \
-e WORDPRESS_DB_HOST=mysql:3306 -e WORDPRESS_DB_NAME=wordpress \
-e WORDPRESS_DB_USER=wordpress -e WORDPRESS_DB_PASSWORD=password \
–name wordpress wordpress:5.2.3-php7.3-apache

### Dockerエンジンの内部構造
Dockerd(デーモン)、containerd(コンテナとイメージ管理)、containerd-shim, runc, コンテナデータ・イメージ

うむー

[Docker] bind

$ sudo docker run -d –rm -p 8080:80 nginx:1.17.6-alpine
$ sudo docker container ls –format=’table {{.ID}}\t{{.Names}}\t{{.Ports}}’
$ sudo docker container port 09ee61149e2d
80/tcp -> 0.0.0.0:8080

Web系サービスを展開する場合、HTTPSの443とHTTP80が外部に公開される

コンテナ間の連携を行う場合は、bridgeではなく、作成したネットワークを利用する
作成したNATのネットワークが、内部のコンテナが他のコンテナを名前解決できる

### コンテナのログを設定
$ sudo docker container run –rm –name nginx -d nginx:1.17.6-alpine
$ sudo docker exec nginx cat /etc/nginx/nginx.conf
error_log /var/log/nginx/error.log warn;
access_log /var/log/nginx/access.log main;
エラー系のログが/var/log/nginx/error.logに書かれ、アクセスログが/var/log/nginx/access.logに書かれている

$ sudo docker exec nginx ls -l /var/log/nginx/error.log
lrwxrwxrwx 1 root root 11 Nov 20 2019 /var/log/nginx/error.log -> /dev/stderr
$ sudo docker exec nginx ls -l /var/log/nginx/access.log
lrwxrwxrwx 1 root root 11 Nov 20 2019 /var/log/nginx/access.log -> /dev/stdout

stderr, stdoutに出力されているのがわかります。

### bind
$ echo “hello bind” > hello1.txt
$ sudo docker run –rm -d –name bct \
–mount type=bind,source=/home/vagrant/dev/docker/book,target=/bindcont \
alpine:3.10.3 tail -f /dev/null
$ sudo docker exec bct ls /bindcont

$ sudo docker container exec bct touch /bindcont/hello2.txt
$ sudo docker stop bct
$ ls

readonlyにすることが多い

なるほど、標準出力、標準エラー出力はエイリアスが設定されてるんか…
標準出力は少し理解してきた

[Docker] 環境変数

nginxをリバースプロキシとして使用する
リバースプロキシーはロードバランサの一種で、クライアントからのアクセスを別のサーバに転送する

### 環境変数を使ったイメージのパラメーター
環境変数はシステムやアプリのパラメータを設定するもので、「キーとバリュー」の形式で管理されている。

アプリサーバー: 表示するメッセージ、ポート番号
リバースプロキシー: アプリサーバーのアドレス、待ち受けポート

pythonではosモジュールのenviron変数を使うことが一般的

server.py

import os, flask
MESSAGE = os.environ['MESSAGE']
PORT = int(os.environ['PORT'])

app = flask.Flask('c2env1_app')
@app.route('/')
def index():
	return MESSAGE

app.run(debug=True, host='0.0.0.0', port=PORT)

$ sudo docker run –name c2env1_app_base -d python:3.7.5-slim tail -f /dev/null
$ sudo docker exec c2env1_app_base pip install flask==1.1.1
$ sudo docker cp server.py c2env1_app_base:/
$ sudo docker stop c2env1_app_base
$ sudo docker commit c2env1_app_base c2env1_app
$ sudo docker run –name c2env1_app -p 8081:80 -d \
-e MESSAGE=”Hello Docker Env” -e PORT=80 \
c2env1_app python -u /server.py

nginx.tpl

events {
	worker_connections
}
http {
	server {
		server_name localhost:
		listen {{PORT}};
		proxy_set_header Host $host;
		proxy_set_header X-Real-IP $remote_addr
		proxy_set_header X-Forwarded-Host $host
		proxy_set_header X-Forwarded-Server $host
		proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
		location / {
			proxy_pass {{APP_SERVER}};
		}
	}
}

start.sh

#!/bin/sh
sed -e "s/{{PORT}}/$PORT/g" /etc/nginx/nginx.tpl > /etc/nginx/nginx.conf
sed -i -e "s^{{APP_SERVER}}^$APP_SERVER^g" /etc/nginx/nginx.conf
exec nginx -g "daemon off;"

$ sudo docker run –name c2env1_web_base -d nginx:1.17.6-alpine tail -f /dev/null
$ sudo docker cp start.sh c2env1_web_base:/
$ sudo docker exec c2env1_web_base chmod +x /start.sh
$ sudo docker cp nginx.tpl c2env1_web_base:/etc/nginx/
$ sudo docker stop c2env1_web_base
$ sudo docker commit c2env1_web_base c2env1_web

$ sudo docker run –name c2env1_web -p 8080:80 -d \
-e APP_SERVER=”http://172.17.0.2:80″ -e PORT=80 \
c2env1_web /start.sh

この様にも書ける
.env

APP_SERVER="http://172.17.0.2:80"
PORT=80

$ sudo docker run –name c2env1_web -p 8080:80 -d \
–env-file .env c2env1_web /start.sh

.envに変数入れて、–env-file .envでコマンド打てば、環境変数として扱えるのね。
これ凄いわ… ガチでビビるレベルや…

[Docker] イメージの検索

### docker imageの検索
$ sudo docker serach python
$ sudo docker search -f “is-official=true” -f “stars=50” python
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
python Python is an interpreted, interactive, objec… 7220 [OK]
pypy PyPy is a fast, compliant alternative implem… 314 [OK]

### curlによるタグ一覧を取得
$ sudo apt install jq
$ curl -s https://registry.hub.docker.com/v1/repositories/python/tags | jq ‘.[].name’
latestは最新版とは限らないので、タグを指定した方が良い

### pull
$ sudo docker pull python:3.7.5-slim
$ sudo docker image inspect python:3.7.5-slim
$ sudo docker rmi mysql:5.7

凄いな、レベルが高いのは一瞬でわかるな…
参ったぜ…

[Docker] コンテナのログを出力する

1. まずコンテナを起動
$ sudo docker run -dit –name myphp -p 8080:80 myphpimage
http://192.168.56.10:8080/

2. コンテナIDを確認
$ sudo docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
090665612e7b myphpimage “/bin/sh -c ‘/usr/sb…” 5 minutes ago Up 5 minutes 0.0.0.0:8080->80/tcp, :::8080->80/tcp myphp

3. ログを確認
$ sudo docker logs -f 090665612e7b
AH00558: apache2: Could not reliably determine the server’s fully qualified domain name, using 172.17.0.2. Set the ‘ServerName’ directive globally to suppress this message

あれ…??? 違うimageでやってみます。
$ sudo docker build . -t myhttpd
$ sudo docker images;
$ sudo docker run -dit –name myhttpd -p 8080:80 myhttpd
$ sudo docker ps
$ sudo docker logs -f 6e69ac1678b5
AH00558: httpd: Could not reliably determine the server’s fully qualified domain name, using 172.17.0.2. Set the ‘ServerName’ directive globally to suppress this message
AH00558: httpd: Could not reliably determine the server’s fully qualified domain name, using 172.17.0.2. Set the ‘ServerName’ directive globally to suppress this message
[Sun Mar 27 06:02:28.200386 2022] [mpm_event:notice] [pid 1:tid 139803119992128] AH00489: Apache/2.4.53 (Unix) configured — resuming normal operations
[Sun Mar 27 06:02:28.201876 2022] [core:notice] [pid 1:tid 139803119992128] AH00094: Command line: ‘httpd -D FOREGROUND’
192.168.56.1 – – [27/Mar/2022:06:02:31 +0000] “GET / HTTP/1.1” 200 213
192.168.56.1 – – [27/Mar/2022:06:03:01 +0000] “GET / HTTP/1.1” 304 –
192.168.56.1 – – [27/Mar/2022:06:03:53 +0000] “-” 408 –

$ docker logs -f ${コンテナID} で出力するのか

### アクセスログのみ表示したい時
$ sudo docker logs 6e69ac1678b5 -f 2>/dev/null
-> 2はエラーログで、/dev/nullは非表示

### エラーログのみ表示したい時
$ sudo docker logs 6e69ac1678b5 -f 1>/dev/null

Dockerのログはコンテナの標準出力(1), 標準エラー出力(2)に書き込まれた内容を表示する

phpのエラーログ
php.iniを以下の様に変更する

error_log = /dev/stderr
[/php]

なるほどー すげー 少しずつ理解できてきた^^

[Docker Hub] イメージを登録する

1. リポジトリを作成する
ddddocker/myexample

2. Dockerイメージ名を調整する
$ sudo docker tag myphpimage ddddocker/myexample:v1

3. リポジトリログイン
$ sudo docker login

4. イメージ登録
$ sudo docker push ddddocker/myexample:v1

なるほど、なんとなく基礎はわかってきた。
あとはdockerfileのベストプラクティスとデプロイCI/CDのところか...