注目の記事

社内勉強会 はじめてのDocker for インフラエンジニア

Dockerを学ぶための社内勉強会を開催しました。資料を公開します。
2021.11.09

こんにちは。
ご機嫌いかがでしょうか。
"No human labor is no human error" が大好きな ネクストモード株式会社 の吉井です。

Docker に触れたことがないインフラエンジニア向けに勉強会を開催しました。
ローカルで Docker を動かし、インフラっぽい動作確認を行い、Amazon ECS で動かすところまでを紹介します。

Cloud9 ロールの作成

EC2 インスタンスプロファイルです。Cloud9 のインスタンスで使用します。
ロール名は EC2Cloud9Role としました。(任意に変更してOK)

マネジメントコンソール IAM ロール を開きます。

ロールの作成 をクリックします。

ユースケースの選択一般的なユースケースEC2 を選択して、次のステップ へ進みます。

Attach アクセス権限ポリシー画面で割り当てるポリシーは以下です。
※ 勉強会用です。本番アカウントでは適切に権限を絞ってください。

  • AmazonEC2ContainerRegistryFullAccess
  • AmazonECS_FullAccess
  • IAMFullAccess

このあとはロール名を入力してロールを作成します。

Cloud9 インスタンスの作成

勉強会ということで時間単価が安いオレゴンリージョン(us-west-2)を使います。
マネジメントコンソールでオレゴンリージョンに切り替えてください。

Cloud9 画面 を開きます。

Create environment をクリックします。

任意の名称を入力して、次へ進みます。

Environment Type は Create a new EC2 instance for environment (direct access) を選択します。

Network Setting を展開して、任意の VPC とパブリックサブネットを選択します。

インスタンスプロファイルの作成

EC2 インスタンス画面 を開くと aws-cloud9-xxxx というインスタンスが起動しているはずです。
そのインスタンスに前の手順で作成したインスタンスプロファイルを関連付けます。

AMTC 無効

AWS Managed Temporary Credentials (AMTC) を無効にしておきます。
無効にする方法は以下に詳しいです。

Cloud9からIAM Roleの権限でAWS CLIを実行する

Dockerのインストール

これ以降は Cloud9 で手順を実行してください。

まずは docker がインストールされていることの確認をします。

docker -v

万が一インストールされていなければインストールします。

sudo yum install -y docker

ハンズオンディレクトリを作ってみよう

ハンズオン用のディレクトリを作っておきます。

mkdir handson
cd handson

Nginxを動かしてみよう

docker run コマンドを打ってみましょう。

docker run --name web -d -p 80:80 nginx

コマンド引数の意味は以下の通りです。

引数 意味
--name コンテナに名前を割り当て。
-d バックグランドで実行。
-p ポートのバインドと公開です。ローカルホストのポート:コンテナのポート の順で指定します。
nginx イメージを指定。今回は Docker Hub 上の nginx イメージを取得しています。

ローカルに Web サーバーが起動したはずです。
curl コマンドで確認してみます。

curl http://localhost/

「Welcome to nginx!」というデフォルトのサンプルページが表示されました。

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
~~省略~~

実行中のコンテナを表示

docker ps コマンドで実行中のコンテナを表示します。
-a を付けると停止しているコンテナも含めて表示になります。

docker ps -a

コンテナが表示されました。
docker run --name で指定した名前が NAMES 列で確認できます。
起動しているコンテナなので STATUS は UP です。
PORTS 列ではローカルホストのポートとコンテナポートのバインドが確認できます。

CONTAINER ID   IMAGE     COMMAND                  CREATED         STATUS         PORTS                               NAMES
4477a272ce0c   nginx     "/docker-entrypoint.…"   3 minutes ago   Up 3 seconds   0.0.0.0:80->80/tcp, :::80->80/tcp   web

実行中のコンテナを停止する

コンテナを停止するコマンドです。
stop は SIGTERM を送信し、一定期間後に SIGKILL を送信します。

docker stop web

docker ps コマンドでコンテナを表示します。

docker ps -a

STATUS が「Exited」に変わっています。

CONTAINER ID   IMAGE     COMMAND                  CREATED         STATUS                      PORTS     NAMES
4477a272ce0c   nginx     "/docker-entrypoint.…"   5 minutes ago   Exited (0) 52 seconds ago             web

コンテナを削除する

起動や停止、削除が容易なことがコンテナのメリットの一つです。
コンテナは気軽に削除しましょう。
仮想サーバーと違いコンテナを停止したまま置いておくことにたいした意味は無いはずです。

docker rm web

何も表示されなくなりました。(つまり削除された)

$ docker ps -a
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

docker rm を毎回打つのが面倒な場合は
docker run 時に --rm を付けることでコンテナ終了時に自動削除することも可能です。

Dockerfileを作ってみよう

これまでの手順では出来合いの nginx を使ってコンテナを起動していました。
実際にサービスを提供するとなると、出来合いのコンテナイメージだけでは不十分です。
様々なカスタマイズを行うのですが、docker run の引数指定だけでカスタマイズを行うのは現実的ではありません。

Dockerfile という便利なものが用意されていますので、これをフル活用していきます。

"FROM nginx" と一行だけ書かれた Dockerfile を作ります。
FROM 行はベースイメージをどこから持ってくるかの指定です。
今回は Docker Hub 上の公式 nginx イメージを使う指定です。

echo "FROM nginx" > Dockerfile

Cloud9 の左側メニューのエクスプローラーで作ったばかりの Dockerfile を開きます。

image

Dockerfile には以下の1行が書かれていると思います。

FROM nginx

Docker ビルド

Dockerfile を作っただけでは何も変化はありません。
ビルドを行い独自のイメージを作成します。

以下のコマンドでビルドします。

docker build -t testweb .
引数 意味
-t イメージ名:タグ 形式でイメージを作成。今回のようにタグを省略すると latest というタグが付与されます。
. Dockerfile のパス。. (ドット)はカレントディレクトリを示します。

作成したイメージを表示するコマンドです。

docker images

-t で指定した「testweb」でイメージが登録されています。

REPOSITORY      TAG          IMAGE ID       CREATED        SIZE
testweb         latest       87a94228f133   3 weeks ago    133MB

それでは、ビルドしたイメージでコンテナを起動します。
緊張しますね。

docker run --name testweb -d -p 80:80 testweb:latest

curl で確認してみましょう。

curl http://localhost/

自分でビルドしたイメージを使えるようになると初心者を脱したような気持ちになれます。
Docker が楽しくなってくると思います。

カスタマイズしてみよう

Dockerfile をビルドしました。
が、まだ「素の nginx」を起動しただけです。
続いてカスタマイズをしていきます。

Dockerfile に COPY 行を追記します。
ローカルにある index.html をコンテナの /usr/share/nbinx/html/ にコピーするという意味です。

FROM nginx

COPY index.html /usr/share/nginx/html/

index.html をローカルに作ります。

echo "hello hello" > index.html

Dockerfile を更新した後は、ビルドを行います。
前の手順で起動したコンテナは不要になるので削除しています。
ビルド~削除~起動~ curl まで一気に実行してしまいましょう。

docker build -t testweb .
docker rm -f testweb
docker run --name testweb -d -p 80:80 testweb:latest

curl http://localhost/

作成した index.html が期待通りに表示されました。

hello hello

演習

index.html に自分の名前を書いて curl で表示してみましょう。

コンテナのなかに入ってみよう

今回の記事はインフラエンジニア向けに書いています。
インフラエンジニアたる者、やはり動いているもののなかには入っていきたいですよね!

動いているコンテナを覗いてみましょう。
実際にもローカルでビルドして動作確認する、デバッグするということは珍しくありません。
以下のコマンドを覚えておきましょう。

exec -it に続けてコンテナ名、呼び出すプログラムを指定します。
/bin/bash を指定することでインタラクティブに bash を操作します。
イメージによってシェルは異なります。今回使っている nginx イメージは bash なだけです。

docker exec -it testweb /bin/bash

ls コマンドや cd コマンドを打ってみてください。コンテナのなかを旅できます。
コピーした index.html を cat することもできます。

cat /usr/share/nginx/html/index.html

exit で抜けます。

exit
docker rm -f testweb

ログを確認しよう

コンテナでは何らかのプロセスが起動しています。(今回は nginx)
トラブルシュートのためにログを読みたいです。
コンテナは基本的に標準出力や標準エラーをログ出力先に指定します。

コンテナのログを読むコマンドが docker logs です。

docker logs testweb

nginx ではアクセスログを標準出力に出しています。
curl でアクセスするたびにログが増えていくことが確認できるはずです。

Dockerイメージにタグを付けよう

Docker イメージはタグを付けることが可能です。
タグはビルドするたびにユニークな文字列を付与していきます。
バージョンを数字でインクリメントしていく、日付時刻を付与する、Git と連携しているならコミット ID を付与するなど様々なタグ付け方法が世の中に溢れています。

今までの手順で作成したイメージを見てみます。

docker images testweb
REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
testweb      latest    fb387cb2215e   24 minutes ago   133MB

タグは「latest」になっています。これがデフォルトです。
ただ、latest は極力使わないようにしましよう。
latest タグでコンテナを起動させると、どの状態でビルドされたイメージなのか分からず、トラブルシュート時に困ります。
また、クラウド上でコンテナを実行させる際にも動作確認前のイメージが予期せずデプロイされてしまう事故にもなりかねません。

タグを付けるコマンドは docker tag です。
タグ付けする元イメージ、新しいタグ の順で引数を指定します。

docker tag testweb:latest testweb:v1

上のコマンドで v1 のタグを付けました。
確認してみます。

docker images testweb

下のような結果になるはずです。
確かに v1 のタグが付いています。

REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
testweb      latest    fb387cb2215e   26 minutes ago   133MB
testweb      v1        fb387cb2215e   26 minutes ago   133MB

ビルド時にタグ付けるすることも可能です。
基本的にはビルド時にタグ付けするようにしましょう。

docker build -t testweb:v2 .

コンテナを起動する際は docker run でタグ付けイメージを指定します。

docker run --name testweb -d -p 80:80 testweb:v2

Dockerイメージを削除してみよう

使わなくなった古いイメージを削除します。
イメージは /var/lib/docker に溜まっていきます。使わないイメージは削除しましょう。

docker rmi testweb:v1

ローカルで悪戦苦闘していると REPOSITORY が none のイメージが作成されることがあります。
サクっと消してしまいましょう。

docker image prune

演習

今日の日付をタグにしたイメージをビルドしてみましょう。
そのイメージで docker run してログを確認してみましょう。
コンテナを停止してそのイメージは削除しましょう。

ECRへプッシュしてみよう

ローカルでビルドが完了したイメージはリポジトリにプッシュします。
今回は AWS マネージドな ECR へプッシュします。

勉強会ということで時間単価が安いオレゴンリージョン(us-west-2)を使います。
4行目の MYNAME 変数は自分の名前を代入します。(半角英数ハイフンのみを使用してください)

sudo yum install jq -y

export AWS_DEFAULT_REGION=us-west-2
export MYNAME=your_name

aws ecr create-repository --repository-name ${MYNAME}

以下のコマンド実行し Login Succeeded と表示されることを確認します。

eval $(aws ecr get-login --no-include-email)

ローカルのイメージ名を ECR リポジトリ名に変更します。
ECR へプッシュするために必要な操作です。
リポジトリ URL は長いので describe-repositories で取得してきます。

REPOURL=$(aws ecr describe-repositories --repository-name ${MYNAME} | jq -r .repositories[].repositoryUri)

docker tag testweb:v2 ${REPOURL}:v2

イメージ名を確認してみます。

docker images

登録されています。問題ありません。
※ ECR のイメージだけに絞って例示しています。

REPOSITORY                                            TAG       IMAGE ID       CREATED       SIZE
123456789012.dkr.ecr.us-west-2.amazonaws.com/yoshii   v2        fb387cb2215e   3 hours ago   133MB

docker push コマンドで ECR へ送り込みます。

docker push ${REPOURL}:v2

マネジメントコンソールで ECR を開き、自分の名前のリポジトリ内に「v2」とタグ付けされたイメージが存在することを確認します。

image

ECSで動かしてみましょう

ECS クラスター&サービスを作成してコンテナを起動します。
今回は ECS CLI を使ってみることにします。

AWS ECS CLI のインストール

Cloud9 インスタンスに ECS CLI をインストールします。

sudo curl -Lo /usr/local/bin/ecs-cli https://amazon-ecs-cli.s3.amazonaws.com/ecs-cli-linux-amd64-latest
sudo chmod +x /usr/local/bin/ecs-cli
ecs-cli --version

実行ロールの作成

ECS 実行ロールを作成します。
ロールには「AmazonECSTaskExecutionRolePolicy」という管理ポリシーを付与します。

cat <<EOF > task-execution-assume-role.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "ecs-tasks.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF

aws iam --region us-west-2 create-role \
  --role-name ecsTaskExecutionRole_${MYNAME} \
  --assume-role-policy-document file://task-execution-assume-role.json

aws iam --region us-west-2 attach-role-policy \
  --role-name ecsTaskExecutionRole_${MYNAME} \
  --policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy

マネジメントコンソールで VPC 画面を開きます。
Cloud9 と同じ VPC とサブネットを特定します。
VPC-ID とサブネット ID を調べて環境変数に代入します。
※ サブネットはパブリックサブネットを2つ指定してください。1つしかサブネットが無い場合は作ってください。

VPCID=vpc-xxxxxxxxxxxxxxxxx
SUBNET1=subnet-xxxxxxxxxxxxxxxxx 
SUBNET2=subnet-yyyyyyyyyyyyyyyyy

セキュリティグループを作ります。
80番ポートを開放しています。

SGID=$(aws ec2 create-security-group --group-name ecs_${MYNAME} --description "My security group" --vpc-id ${VPCID} | jq -r .GroupId)

aws ec2 authorize-security-group-ingress \
    --group-id ${SGID} \
    --protocol tcp \
    --port 80 \
    --cidr 0.0.0.0/0

ECS クラスターを作成します。

ecs-cli configure --cluster ${MYNAME} --default-launch-type FARGATE --config-name ${MYNAME} --region us-west-2

ecs-cli up --cluster-config ${MYNAME} --ecs-profile ${MYNAME}-profile \
  --subnets ${SUBNET1},${SUBNET2} \
  --vpc ${VPCID}

コマンド出力の最終行に「secceeded」が表示されることを確認します。
マネジメントコンソールで ECS クラスターを確認してみてください。

Cluster creation succeeded.

ECS サービスを作成します。
これが成功するとコンテナが AWS 上で起動します。

cat <<EOF > docker-compose.yml
version: '3'
services:
  web:
    image: ${REPOURL}:v2
    ports:
      - "80:80"
    logging:
      driver: awslogs
      options: 
        awslogs-group: ${MYNAME}
        awslogs-region: us-west-2
        awslogs-stream-prefix: web
EOF

cat <<EOF > ecs-params.yml
version: 1
task_definition:
  task_execution_role: ecsTaskExecutionRole_${MYNAME}
  ecs_network_mode: awsvpc
  task_size:
    mem_limit: 0.5GB
    cpu_limit: 256
run_params:
  network_configuration:
    awsvpc_configuration:
      subnets:
        - "${SUBNET1}"
        - "${SUBNET2}"
      security_groups:
        - "${SGID}"
      assign_public_ip: ENABLED
EOF
ecs-cli compose --project-name ${MYNAME} service up \
  --create-log-groups \
  --cluster-config ${MYNAME} \
  --ecs-profile ${MYNAME}-profile

ECS サービスの状態を確認します。

ecs-cli compose --project-name ${MYNAME} service ps \
  --cluster-config ${MYNAME} \
  --ecs-profile ${MYNAME}-profile

docker ps と似たような出力が表示されます。
Ports 列にパブリック IP アドレスが表示されています。これをコピーしブラウザで開きます。
※ 商用環境では前段に ELB を配置しますが今回は勉強会なのでコンテナ直接アクセスです。

Name                                         State    Ports                     TaskDefinition  Health
yoshii/2fc68bd0d6d2458e9c2709dde99d0fbf/web  RUNNING  nn.nn.nn.nn:80->80/tcp  yoshii:1        UNKNOWN

演習

マネジメントコンソールで ECS サービスやタスクがどのように表示されるか確認します。
CloudWatch Logs にコンテナのログが保管されています。ログ内容を表示します。

クリーンアップ

無駄な課金を防ぐためにリソースはクリーンアップします。

ecs-cli compose --project-name ${MYNAME} service down \
  --cluster-config ${MYNAME} \
  --ecs-profile ${MYNAME}-profile

ecs-cli down --force --cluster-config ${MYNAME} --ecs-profile ${MYNAME}-profile

その他のリソースは手動で削除します。

  • セキュリティグループ
  • CloudWatch Logs
  • ECR
  • Cloud9

おまけ

みなさまが自チームで同じような勉強会を開催する助けになればと思い、GitHub で記事のソースを公開しました。