コンテナといえば Docker です。Docker は別に怖くないし、Docker がないと何も始まらないです。
ここでは開発の準備と、Docker で簡単なイメージの作成を行っていきます。
目次
(0) はじめに(1) Docker ← イマココ!
(2) イメージ自動生成
(3) サービス起動
(4) デプロイ
(5) Auto Scaling
(6) 費用と性能
開発準備
主に Git, Docker, Terraform を使って構築していきます。OS は Docker が動けば大丈夫なので、どのディストリビューションでもよいのですが、一応カーネル・バージョンに条件があるので、せっかくなのでわりと新し目の環境でやるとよいです。今回は、CentOS7.4 としてパッケージ関連を記述するので、各自変換する感じでお願いします。進行図
手元にこれが揃えば大勝利だ!
Git
AWSの CodeCommit や GitHub を使うために必要です。てか、入ってるっしょ。
1 |
yum install git |
Terraform
AWSのリソース管理のために使います。管理画面ポチポチは最初の遊びだけにしましょう。Terraformでなくてもよいですが、どのようなリソースがどのように関連しているか、を理解しやすいので具体的なコードを記載していきます。
1 2 3 4 5 6 7 8 9 10 11 |
# バージョン管理しやすいように tfenv を入れます git clone https://github.com/kamatama41/tfenv.git ~/.tfenv echo 'export PATH="$HOME/.tfenv/bin:$PATH"' >> ~/.bash_profile source ~/.bash_profile # terraform をインストールします。latest と指定すると beta が入ったりするので、 # https://github.com/hashicorp/terraform/blob/master/CHANGELOG.md # をみて、正式リリース版を指定してください tfenv install 0.11.11 tfenv list terraform version |
AWS CLI
別に必須じゃないですが、せっかくなので aws コマンドのインストールもしておきます。
1 2 3 4 5 6 7 8 |
yum install epel-release yum --enablerepo=epel install python-pip pip install --upgrade pip pip install awscli aws --version # アップデート pip install -U awscli |
Docker
コンテナを動かすのに Docker デーモンが動いている必要があります。標準パッケージは古く、現在は CE(コミュニティエディション)とEE(エンタープライズエディション)が提供されているので、Docker CE をインストールして動かしておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# インストールします yum install yum-utils device-mapper-persistent-data lvm2 yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo yum install docker-ce docker-ce-cli containerd.io docker version # 一般ユーザーでは docker コマンドでコンテナを起動したりできないので、 # グループに所属させることで実行できるようにします(ここだけ一般ユーザー表記) $ sudo gpasswd -a $USER docker # デーモンを起動しっぱにします systemctl enable docker systemctl start docker systemctl status docker # root でもグループに入れた一般ユーザーでもコマンドを実行できます # これはローカルイメージのリストなので、まだ空 docker images |
豆ですが、データディレクトリ /var/lib/docker をそれなりに利用するので、別パーティションを利用したい場合は、
1 2 3 4 5 6 |
vim /usr/lib/systemd/system/docker.service # で以下のように -g オプションでパスを指定して再起動します ExecStart=/usr/bin/dockerd -g /data/docker # 本当は /etc/sysconfig/docker を使うようにするのが正着ですが、開発環境なので簡略 |
Dockerで遊ぶ
最初は Docker の詳しい仕組みはどうでもいいです。親OSの上で動く子OSがコンテナ、程度の認識で十分で、楽しくなってきたら他の仮想化技術との違いとか歴史を学びましょう。まずは、コンテナが動くことを確認します。コンテナを動かすには、まず元になるイメージが必要です。昔の仮想化技術を扱うときは、よく Linux のインストールイメージから起動して自分で作成したものですが、Docker には Docker Hub なる神サービスが存在していて、そこからスタートすることになります。
1 2 3 4 5 6 7 8 |
# Hub から CentOS を探してみる docker search centos # Debian 系とか docker search debian # 最強と名高い alpine docker search alpine |
Docker を扱う時に必ず重要なテーマとなる1つに、イメージサイズがあります。CentOS や Ubuntu を扱うと、どうしても最低サイズが数百MB 以上になるのですが、それだといざ本番稼働したときに、転送時間や転送量課金がボディブローのように響いてきます。そこで名乗りを上げるのが alpine です。
一般的な Linux をインストールするときも、「最小構成で」みたいな選択がありますが、それよりも遥かな微小構成の alpine は、一桁MB からのスタートとなります。パッケージ管理が apk、シェルは /bin/busybox が基本となりますが、慣れの問題でありたいしたことではありません。開発システムを突き詰めていくと、もしかしたら実現できない事象にぶち当たる可能性はありますが、可能な限り最初から alpine で始めると、一味違うDocker使いになれるとか、なれないとか。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# コンテナを起動してみる docker run alpine # alpine:latest がローカルにないので、ダウンロードしつつ起動されます # ローカルイメージを確認 docker images # 起動されたコンテナを確認 docker ps -a # とりあえず起動中のコンテナを全部止めて削除するワンライナー docker stop `docker ps -aq` && docker rm `docker ps -aq` |
これだと、イメージ取得しただけで、コンテナには何も触れていません。
Docker の扱いには面白い特徴があり、
というものがあります。とはいえ、Linux を扱うのにシェル作業がゼロというわけにはいかないので、中にお邪魔したい場合は……
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# -it をつけると /bin/sh -> /bin/busybox を実行しっぱなしにできる docker run -it alpine # いろいろ実行できる ls -l df -Th free # インターネットにアクセスするために curl 入れてアクセスしてみる apk add curl curl http://example.com/ # run のオプションを確認したり exit docker run --help # alpine イメージの詳細をみてみる docker inspect alpine |
なぜ、このコマンドで作業できたかは、run –help でオプションの説明を確認し、inspect でイメージの設定を見れば理解できます。設定の Cmd という項目に /bin/sh と記述されており、上書き指定しない場合はこれが起動時に実行されるコマンドとなるからです。それを、-it によって持続処理とした形です。ちょっと楽しくなってきましたね。
Dockerfileで独自イメージを作る
Dockerもいろんな使い方があるんでしょうが、多くは任意の処理を何度も、もしくは並列で行わせるのが目的になると思います。定期的に同じジョブを実行するとか、並列でWEBサーバーを起動するとか、なんでもよいのですが、ここではWEBサーバーでいきましょう。Dockerfile という名前のファイルに、元イメージや任意の処理、最後に実行しておきたいデーモン、などをズラッと書くことで、自分の好きな処理をさせる独自イメージを作成することができます。そのイメージを、何かしらのコンテナ管理システムにブチ込むことで運用していく感じです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
# 作業ディレクトリを作って mkdir -p ~/docker/nginx cd ~/docker/nginx/ # 簡単な Dockerfile をつくる vim Doockerfile # 中身はこんな感じで # ----- ここから ----- FROM alpine:latest RUN apk --update add --no-cache nginx && \ adduser -D -g 'www' www && \ mkdir /www /run/nginx && \ chown -R www:www /var/lib/nginx /www VOLUME ["/var/cache/nginx"] EXPOSE 80 ADD nginx.conf /etc/nginx/ ADD index.html /www/ CMD ["nginx", "-c", "/etc/nginx/nginx.conf", "-g", "daemon off;"] # ----- ここまで ----- # Nginx の設定を適当に # https://wiki.alpinelinux.org/wiki/Nginx # からもってきたやつ vim nginx.conf # ----- ここから ----- user www; worker_processes auto; # it will be determinate automatically by the number of core error_log /var/log/nginx/error.log warn; #pid /var/run/nginx.pid; # it permit you to use /etc/init.d/nginx reload|restart|stop|start events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; sendfile on; access_log /var/log/nginx/access.log; keepalive_timeout 3000; server { listen 80; root /www; index index.html index.htm; server_name localhost; client_max_body_size 32m; error_page 500 502 503 504 /50x.html; location = /50x.html { root /var/lib/nginx/html; } } } # ----- ここまで ----- # 好きなHTMLを書いておく vim index.html # ----- ここから ----- <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <title>TEST</title> </head> <body> Lifeguard is super bionic drink !! </body> </html> # ----- ここまで ----- # イメージをビルドします # -t でイメージ名を指定し、使用設定はカレントディレクトリ docker build -t alpine-nginx . # Successfully tagged alpine-nginx:latest って出たらOK # イメージを確認。これでもまだ7MB程度 docker images # 起動してみます # 手元の作業OSからコンテナにアクセスするために、ポートフォワーディングします docker run -d -p 10080:80 alpine-nginx docker ps -a # 親OSの10080番にアクセスしたら、コンテナの80に接続されます curl http://localhost:10080/ |
ベースイメージとアプリイメージを作成する
アプリケーションを作成したあとの運用にて、さらなる開発とデプロイを何度も繰り返すことになりますが、1回あたりのデプロイやコンテナの起動速度は速いほど良いのは当然です。コンテナは処理の冪等性という安心感が強みですが、速度にも気を配ることで物理的な時間の短縮だけでなく、運用者の精神的な障壁を低くする効果があります。そのため、ここではいわゆるミドルウェア類の構築と、アプリケーションのデプロイを分けて構成していきます。デプロイしたいだけなのに毎回ミドルウェアのインストールを走らせる必要はないでしょう?そういった細かい工夫の積み重ねが、最終的なコンテナ運用の品質に大きく影響してくるっぽいですよ。
ということで、2つのイメージをつくるコードはウチのGitHubから落としてきて、1つめの base イメージを作成し、それを元に2つめの application イメージをつくって動作確認までします。内容は簡素なもので、Nginx -> Ruby on Rails unicorn を動かすだけのものになっています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
# まず1つめのコードを取得して # ※buildspec.yml はあとで使うので今は気にしないでください cd ~/docker/ git clone git@github.com:GedowFather/docker-ecr-base.git cd docker-ecr-base/ # 中身の確認とかして、ビルドします ls -l less Dockerfile docker build -t alpine-base . # 確認。120MB弱 docker images # この時点では何も動かないコンテナなのでそのまま次に行ってもいいですが、 # ちゃんと入ったか気になるなら起動してシェルでみてきましょう docker run -it --entrypoint=/bin/sh alpine-base # 終わったらお掃除 exit docker stop `docker ps -aq` && docker rm `docker ps -aq` |
Dockerfile をザッと眺めると気になる点がいっぱいあると思います。その扱い方については最初のリンク集やググったりで調べてほしいところですが、最も重要なポイントの1つに軽量化があります。
ディストリビューションが変わると方法は変わりますが、不要なものは残さない、という点で共通です。alpine の場合は、apk add の際に –no-cache をつけることでキャッシュしないようにし、またコンパイルのためだけに必要なパッケージなどは最後に削除しています。例えば ruby は rubyパッケージが無いと動かないけど、gem の mysql2 はインストールに mysql-dev が必要でも、その後は mysql-dev をアンインストールしても gem としては動作するでしょう。従来だと、パッケージはとりあえず入れておけばいい雰囲気があったりしましたが、容量削減には要不要の正しい把握が大切ということです。
では次に、できたイメージを元にアプリ用イメージを作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
# アプリ用のコードを取得し cd ~/docker/ git clone git@github.com:GedowFather/docker-ecr-application.git cd docker-ecr-application/ # また中身を眺めてからビルドします ls -l less Dockerfile less entrypoint.sh docker build -t alpine-application . # 確認。130MB docker images # 今度はデーモンを起動するので、コンテナを起動してHTTP確認まで docker run -d -p 10080:80 alpine-application # これだとNginxの設定上、デフォルトホストの内容になるので curl http://localhost:10080/ # Host を指定すると rails の welcome や、 curl http://localhost:10080/ -H "Host: test.example.com" # 適当に作ったviewでCPUや、後でAWSで使う環境変数を確認できます curl http://localhost:10080/test/index -H "Host: test.example.com" # イメージを何度もビルドしてるとタグ無しイメージが溜まるのでワンライナーお掃除 docker rmi $(docker images -qf dangling=true) --force |
最初のイメージと違って、Dockerfile の最後に ENTRYPOINT の entrypoint.sh があります。これは、先程も述べた、コンテナ継続のためにはフォアグラウンド実行が必要、というルールに従って書いたデーモン群です。Nginx は指定しなければ勝手にバックグラウンドにいき、unicorn はフォアグラウンドなのでこういう記述になっていますが、強制的にフォアグラウンドで終わらせるテクニックの1つとして tail -f /dev/null があるため、コメントアウトして記述しています。
これで無事に2つのイメージができました。アプリケーションを更新したければ、2つめのコードを編集してビルドすればいいですし、ミドルウェアを更新したければ、1つめのコードを編集してビルドし、2つ目のイメージをそのままビルドすることになります。
実際には、アプリケーションのコードは git clone してくるようなイメージになるので、Dockerfileの編集はそう発生するものではないでしょう。まぁそのへんは開発手法やインフラ構成によって変わるので、ここではこの超簡易イメージでいったんフィニッシュです。
AWS ECR にイメージをプッシュしてみる
最後のこの話は、最終的な構成からすると余談になるのですが、Dockerイメージを扱うならば知っておく必要があります。作成したイメージは、Registry に push (=アップロード) することができるので、AWS ECR で練習しておくとよいです。というのも、push 方法が docker コマンドを使う独特なものなので、どの Registry を扱うにせよ基本を知らないと困るからです。が、イメージ作成とアップロードは結局自動化するので、雑にコマンドベースで手順を書くだけにしておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
# IAM で ecr:* なポリシーを持つ User を利用します export AWS_ACCESS_KEY_ID=*** export AWS_SECRET_ACCESS_KEY=*** export REGION=ap-northeast-1 export ACCOUNT_ID=123456789012 # リポジトリを作成します NAME=test-web aws ecr create-repository --repository-name $NAME --region $REGION aws ecr describe-repositories --region ${REGION} # ローカルのイメージにタグを付けます docker tag alpine-nginx ${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/${NAME} docker images # 認証用の文字列を取得します aws ecr get-login --no-include-email --region $REGION # 上記コマンドで表示された docker login コマンドを実行します docker login -u AWS -p *** # アップロードします docker push ${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/${NAME} # CLIや管理画面で確認します aws ecr list-images --repository-name $NAME --region $REGION |
進行図
ここまでやりました。でもこの直接アップロードなんて作業は自動化により、すぐなくなります。
Dockerの理解が進めば、もう勝ったも同然!
次へまいります:イメージ自動生成