Skip to content

Instantly share code, notes, and snippets.

@ozzozz
Last active November 16, 2016 05:41
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ozzozz/2431b8393b9804de4e775ee794d2ae48 to your computer and use it in GitHub Desktop.
Save ozzozz/2431b8393b9804de4e775ee794d2ae48 to your computer and use it in GitHub Desktop.
Docker勉強会

目次

(1) Dockerインストール

(2) CentOSやUbuntuのベースDockerイメージしか入ってないDockerコンテナの起動

(3) Apacheが入ったDockerコンテナでWebサーバを起動

(4) Apache+PHPが入ったDockerコンテナとMySQLが入ったDockerコンテナの2つで簡単なLAMPのWebサイトを起動

(5) DockerfileからのDockerイメージのビルドと起動

(6) Dockerコンテナ内での単体テストの実行

(7) Docker Composeによる複数Dockerコンテナの一括操作

(8) プライベートDockerレジストリの立て方と使い方

(0) 事前準備

  • 南砂クラウド(OpenStack)上に、勉強会参加者1名につき1個、以下の条件で仮想マシンを新規作成しておいて下さい。
    • フレーバ: m4.XS(vCPUx1、MEM4GB、Disk30GB)以上
    • OS: CentOS 7 64bit
      • パブリックイメージに「service_centos7_64bit」というのがありますので、そちらをお使いいただければよいのではと思います。
    • ネットワーク、IPアドレス周りで特別な設定は不要
    • SSHログイン可能としておく
      • 極力事前にチェックしておいて下さい。
    • sudoでroot権限を取得可能としておく
      • 極力事前にチェックしておいて下さい。
    • yumミラーリポジトリにアクセス可能としておく
      • 以下のコマンドを実行し、BaseとUpdatesとExtrasがenableになっていること、アクセス先がミラーリポジトリサーバxadmin-10-pro-svn-001に向いていることを、極力事前にチェックしておいて下さい。
$ sudo yum repolist all
$ sudo yum repoinfo all
  • 通山さんより以下のコメントあり。
NTT-Rでpuppetを使える人なら↓を見せて「VMを作って↓のようなpuppet manifestを適用しておいてください。
yumリポジトリへのアクセスが最低限必要です」とか言えば通じるはずです。

class <svccode>-<env>-<host>_rgc inherits mediainfra-01-common_rgc {
        #必要なLinuxユーザを作成する定義は別途必要

        include 'mediainfra-01-common_rc_hosts'
        include 'mediainfra-01-common_rc_syslog'
        include 'mediainfra-01-common_rc_yumrepos_centos'
        include 'mediainfra-01-common_rc_subversion'
        include 'mediainfra-01-common_rc_disable_services'
        include 'mediainfra-01-common_rc_yum_clean_all'
        include 'mediainfra-01-common_rc_yum_remove_i386'
}

(1) VMへのDockerのインストール

$ sudo yum update

カーネルがアップデートされたら、リブートしておく

$ sudo reboot

yumリポジトリからCentOS7で配布されているDockerエンジン(v1.10)をインストール

$ sudo yum install docker
$ sudo systemctl list-unit-files | grep docker

Dockerサービスに南砂クラウドのproxy設定をエディタで追加

$ sudo vim /etc/sysconfig/docker

以下の行をファイルの末尾に追加

http_proxy=http://172.20.253.21:8080
https_proxy=http://172.20.253.21:8080

Dockerサービスのブート時自動起動を有効にする

$ sudo systemctl enable docker.service

Dockerサービスを起動する

$ sudo systemctl start docker

Dockerサービスの稼働状況を確認する

$ sudo docker info

DockerHubまで通信が通るか確認する

$ sudo docker search hello-world

(2) CentOSやUbuntuのベースDockerイメージしか入ってないDockerコンテナの起動

ねらい: Dockerの仕組みの基本を理解する。

準備

2枚めのターミナル(t2と呼称。1枚目はt1と呼称)を起動し、両方ともVMにSSHログインしておくこと。

CentOS

(t1)DockerHubからCentOSのDockerイメージを取得

t1$ sudo docker pull centos

(t1)取得したDockerイメージからDockerコンテナを作成し、そのコンテナ内でbashを起動。

  • キーボード入力を有効にするための-iオプションと、ターミナルを奪う-tオプションを付ける
t1$ sudo docker run -it centos bash

(t1)コンテナ内でCentOSのバージョンを確認

container# cat /etc/redhat-release

(t2)取得したDockerイメージの一覧を確認

  • CentOSのイメージがあるはず
t2$ sudo docker images

(t2)作成したDockerコンテナの一覧を確認

  • コンテナが1個できているはず
t2$ sudo docker ps

(t1)コンテナ内でプロセスツリーを確認

  • initやsystemdなどがPID=1になっておらず、bashがPID=1になってる
container# ps auxww

(t2)Dockerホスト側でもプロセスツリーを確認

  • プロセスツリーの違いを確認。コンテナ内で起動したbashも見えているはず
t2$ ps auxww

(t1)コンテナ内でボリュームの状況を確認

container# df -m

(t2)Dockerホスト側でもボリュームの状況を確認

  • ボリュームの構造、容量などの違いを確認。マウントポイントが違っていたり、容量が少なかったりするはず
t2$ df -m

(t1)コンテナ内から外向きの通信はちゃんと通ることを確認

container# http_proxy=http://172.20.253.21:8080 curl http://www.goo.ne.jp/

(t2)Dockerホスト側でネットワークインタフェースの状況を確認

  • ホストローカルNWのLinuxブリッジと、コンテナ用のvethができている
t2$ ip a

(t1)コンテナ内でファイルを作ってみる

container# touch hogehoge
container# ls

(t1)コンテナ内のbashを終了する

container# exit

(t1)稼働中のコンテナの一覧を確認

t1$ sudo docker ps

(t1)停止したものも含めて全てのコンテナの一覧を確認

t1$ sudo docker ps -a

(t1)先ほどbashを動かしたコンテナが残留しているが、もう使えないので消す

  • コンテナ名はランダムに生成される
t1$ sudo docker rm [コンテナ名]

(t1)Dockerイメージの一覧を確認

t1$ sudo docker images

(t1)再度コンテナを作成し、コンテナ内でbashを起動

  • bash終了後に残留する使えないコンテナを自動で消すために--rmオプションを付ける
t1$ sudo docker run -it --rm centos bash

(t1)コンテナ内のディレクトリを確認する

  • hogehogeファイルはない
container# ls

(t1)コンテナ内のbashを終了する

container# exit

(t1)停止したものも含めて全てのコンテナの一覧を確認

  • 全て消えているはず
t1$ sudo docker ps -a

ここまででわかったこと

  • プロセスツリーはあやしいが、ボリュームもちゃんとあってディレクトリツリーもあるし、通信もできるし、仮想マシンに似ている(ような気がする)
  • コンテナを消すと、コンテナ内で作っていたファイルも揮発する

Ubuntu

(t1)DockerHubからUbuntuのDockerイメージを取得し、取得したイメージからコンテナを作成し、コンテナ内でbashを起動

  • docker pullコマンドを実行せず、いきなりdocker runコマンドを実行することで、イメージ取得からコンテナ起動まで通しでできる
t1$ sudo docker run -it --rm ubuntu bash

(t1)コンテナ内でUbuntuのバージョンを確認

container# cat /etc/lsb-release

(t1)コンテナ内でLinuxカーネルのバージョンを確認

container# uname -a

(t2)Dockerホスト側でもLinuxカーネルのバージョンを確認

  • コンテナ内で確認したものと同じはず
t2$ uname -a

(t1)Ubuntuコンテナを終了

container# exit

ここまででわかったこと

  • Dockerイメージが異なれば、ディレクトリツリーも異なる
  • LinuxカーネルはDockerホストのものを共有している

後かたづけ

(t1)停止したものも含めて全てのコンテナの一覧を確認

  • 全て消えているはず
t1# sudo docker ps -a

(t1)Dockerイメージの一覧を確認

  • CentOSとUbuntuのDockerイメージが残っているはず
t1$ sudo docker images

(t1)Dockerイメージを削除

  • docker.io/ とか latest は省略可能
t1$ sudo docker rmi centos
t1$ sudo docker rmi ubuntu

(t1)Dockerイメージの一覧を確認

  • 全て消えているはず
t1$ sudo docker images

(3) Apacheが入ったDockerコンテナでWebサーバを起動

ねらい: Dockerコンテナによるサービス提供を体験する。

準備

2枚めのターミナル(t2と呼称。1枚目はt1と呼称)を起動し、両方ともVMにSSHログインしておくこと。

まずは上げてみる

(t1)DockerHubからApache httpd入りのDockerイメージを取得し、取得したイメージからコンテナを作成

  • いきなりhttpdが起動する
  • 起動後はターミナルを奪われたままフォアグラウンドで動作しつづける
t1$ sudo docker run --rm httpd

(t2)Webアクセスしてみる

  • t2には It works! っていうHTMLが返ってくるはず
  • t1にはアクセスログが出力されているはず
t2$ curl 172.17.0.2

(t2)localhostにWebアクセスしてみる

  • t2にはConnection Refusedというエラーが返ってくるはず
  • t1にはアクセスログが出力されてないはず
t2$ curl localhost

(t2)自ホストのIPアドレスをチェックし、そこにWebアクセスしてみる

  • t2にはConnection Refusedというエラーが返ってくるはず
  • t1にはアクセスログが出力されてないはず
t2$ ip a
t2$ curl [自ホストのIPアドレス]

(t1)httpdのコンテナを停止する

^C

(t1)停止したものも含めて全てのコンテナの一覧を確認

  • 全て消えているはず
t1# sudo docker ps -a

(t1)Dockerイメージの一覧を確認

  • Apache httpd入りDockerイメージが残っているはず
t1$ sudo docker images

ここまででわかったこと

  • Dockerイメージを取得できれば、WebサーバなどいろんなプログラムをDockerコンテナ内で簡単に起動できる
  • サービスをただ起動するだけだと、外部からもlocalhostからもアクセスできない

httpdの待受ポートを開放する

(t1)コンテナの待受ポートを開放するための-pオプションを付けて、httpdのコンテナを起動する

t1$ sudo docker run --rm -p 80:80 httpd

(t2)localhostにWebアクセスしてみる

  • t2には It works! っていうHTMLが返ってくるはず
  • t1にはアクセスログが出力されているはず
t2$ curl localhost

(t2)自ホストのIPアドレスをチェックし、そこにWebアクセスしてみる

  • t2には It works! っていうHTMLが返ってくるはず
  • t1にはアクセスログが出力されているはず
t2$ ip a
t2$ curl [自ホストのIPアドレス]

(t2)稼働中のコンテナの一覧を確認

  • PORTSの欄が0.0.0.0:80->80/tcpと表示されている。これはポートフォワーディングが行われているということ
t2$ sudo docker ps

(t1)httpdのコンテナを停止する

^C

(t1)停止したものも含めて全てのコンテナの一覧を確認

  • 全て消えているはず
t1$ sudo docker ps -a

ここまででわかったこと

  • Dockerコンテナで起動したサービスにlocalhostや外部からアクセス可能とするには、ポートフォワーディングを行うための「-p」オプションを使う

コンテナをバックグラウンドで動かす

(t1)コンテナをバックグラウンドで動かすための-dオプションを付けて、httpdのコンテナを起動する

  • --rmオプションは使えないので注意
  • コマンドプロンプトが返ってくる
t1$ sudo docker run -p 80:80 -d httpd

(t1)稼働中のコンテナの一覧を確認

  • httpdのコンテナがバックグラウンドで動いている
  • コンテナに勝手に名前がつけられている
t1$ sudo docker ps

(t1)localhostにWebアクセスしてみる

  • t1には It works! っていうHTMLが返ってくるはず
  • アクセスログは・・・・どこ?
t1$ curl localhost

バックグラウンドで動いているコンテナのログ(標準出力・標準エラー出力)を取得する

(t1)docker logsコマンドを実行

  • 先ほどのアクセスのログも含め、これまでの標準出力・標準エラー出力を見れる
t1$ sudo docker logs [コンテナ名]

(t1)バックグラウンドで動いているコンテナのログをtail -fする

  • コマンドプロンプトは返ってこず、tail -f的にログを見れる
t1$ sudo docker logs -f [コンテナ名]

(t2)localhostにWebアクセスしてみる

  • t2には It works! っていうHTMLが返ってくるはず
  • t1にはアクセスログが出力される
t2$ curl localhost

(t1)ログのtail -fをやめる

^C

(t1)稼働中のコンテナの一覧を確認

  • httpdのコンテナは、まだバックグラウンドで動いている
t1$ sudo docker ps

バックグラウンドで動いているコンテナでコマンドプロンプトをゲットする

(t1)docker execコマンドにより、httpdのコンテナ内でbashを起動

  • ちゃんとログインできる
t1$ sudo docker exec -it [コンテナ名] bash

(t2)localhostにWebアクセスしてみる

  • t2には It works! っていうHTMLが返ってくるはず
t2$ curl localhost

(t1)コンテナ内でプロセスツリーを確認

  • httpdがフォアグラウンドで動いている
container# ps auxww

(t1)index.htmlを確認してみる

  • /usr/local/apache2/htdocs 配下にある
container# cd htdocs
container# cat index.html

(t1)Webサイトに新しいコンテンツを追加してみる

  • viもemacsもないので、echoコマンドを使う
container# echo "hello" > test.txt

(t2)新しいコンテンツを確認してみる

  • ちゃんと入っているはず
t2$ curl localhost/test.txt

(t1)httpdコンテナのbashから抜ける

container# exit

(t1)稼働中のコンテナの一覧を確認

  • httpdのコンテナは、まだバックグラウンドで動いている
t1$ sudo docker ps

バックグラウンドで動いているコンテナを停止・再起動する

(t1)httpdのコンテナを停止する

t1$ sudo docker kill [コンテナ名]

(t1)稼働中のコンテナの一覧を確認

  • httpdのコンテナはもう動いてない
t1$ sudo docker ps

(t1)停止したものも含めて全てのコンテナの一覧を確認

  • httpdのコンテナは停止中で残っている
t1$ sudo docker ps -a

(t1)localhostにWebアクセスしてみる

  • 当然応答はない
t1$ curl localhost

(t1)httpdのコンテナのログ取得を試みる

  • ログは取得できるはず
t1$ sudo docker logs [コンテナ名]

(t1)停止したhttpdのコンテナを再度起動する

t1$ sudo docker restart [コンテナ名]

(t1)稼働中のコンテナの一覧を確認

  • httpdのコンテナが再度動いている
t1$ sudo docker ps

(t1)localhostにWebアクセスしてみる

  • t1には It works! っていうHTMLが返ってくるはず
t1$ curl localhost

(t1)追加したコンテンツtest.txtにもWebアクセスしてみる

  • ちゃんと入っているはず
t2$ curl localhost/test.txt

(t1)httpdのコンテナを停止する

t1$ sudo docker kill [コンテナ名]

(t1)稼働中のコンテナの一覧を確認

  • httpdのコンテナはもう動いてない
t1$ sudo docker ps

(t1)停止したものも含めて全てのコンテナの一覧を確認

  • httpdのコンテナは停止中で残っている
t1$ sudo docker ps -a

(t1)httpdのコンテナを削除する

t1$ sudo docker rm [コンテナ名]

(t1)停止したものも含めて全てのコンテナの一覧を確認

  • httpdのコンテナはもう残ってない
t1$ sudo docker ps -a

ここまででわかったこと

  • Dockerコンテナをバックグラウンドで動かすには、docker runコマンドに「-d」オプションを付ける
  • バックグラウンドで動いているDockerコンテナのログ(標準出力・標準エラー出力)は docker logsコマンドで取得できる
  • バックグラウンドで動いているDockerコンテナ内で、bashなど任意のコマンドを追加実行できる
  • バックグラウンドで動いているDockerコンテナは、docker killコマンドで停止でき、docker restartコマンドで再起動できる
    • この際、前回の動作中に追加・変更されたファイルは残っている
    • 停止したコンテナのログもdocker logsコマンドで取得できる

Dockerホストの特定のディレクトリをコンテナ内のディレクトリにマウントする

事前検討

(t1)再度、httpdのコンテナをバックグラウンドで起動する

t1$ sudo docker run -p 80:80 -d httpd

(t1)稼働中のコンテナの一覧を確認

  • httpdのコンテナがバックグラウンドで動いている
  • コンテナに先ほどとは違う名前が勝手につけられている
t1$ sudo docker ps

(t1)localhostにWebアクセスしてみる

  • t1には It works! っていうHTMLが返ってくるはず
t1$ curl localhost

(t1)test.txtにもWebアクセスしてみる

  • Dockerイメージからコンテナを作り直したので、当然404が返ってくるはず
t2$ curl localhost/test.txt

(t1)docker execコマンドにより、httpdのコンテナ内でbashを起動

t1$ sudo docker exec -it [コンテナ名] bash

(t1)httpdのコンテナ内でディレクトリ構造を確認

  • /usr/local/apache2/htdocs にDockerホストのディレクトリをマウントできれば、コンテナに入らなくてもDockerホストでコンテンツを追加・変更できそう
container# ls

(t1)コンテナから抜ける

container# exit

(t1)httpdのコンテナを停止し削除する

  • docker rm -fコマンドで一気にできる
t1$ sudo docker rm -f [コンテナ名] bash

(t1)停止したものも含めて全てのコンテナの一覧を確認

  • httpdのコンテナはもう残ってない
t1$ sudo docker ps -a

実食

(t1)Dockerホストにマウント対象のディレクトリを作成し、index.htmlファイルを作成

  • index.htmlファイルの中身は適当でOK
t1$ mkdir htdocs
t1$ vim ./htdocs/index.html

(t1)Dockerホストの特定のディレクトリをコンテナ内のディレクトリにマウントするための-vオプションを付けて、httpdのコンテナを起動する

t1$ sudo docker run -p 80:80 -d -v $PWD/htdocs:/usr/local/apache2/htdocs httpd

(t1)localhostにWebアクセスしてみる

  • t1には、先ほど作成したindex.htmlの中身が返ってくるはず
t1$ curl localhost

(t1)Dockerホストのマウントされたディレクトリ内のindex.htmlを編集する

  • 適当に編集
t1$ vim ./htdocs/index.html

(t1)再度localhostにWebアクセスしてみる

  • t1には、編集されたindex.htmlの中身が返ってくるはず
t1$ curl localhost

ここまででわかったこと

  • Dockerホストの特定のディレクトリをコンテナ内のディレクトリにマウントするには、docker runコマンドに「-v」オプションを付ける

後かたづけ

(t1)稼働中のコンテナの一覧を確認

t1$ sudo docker ps

(t1)httpdのコンテナを停止し削除する

  • docker rm -fコマンドで一気にできる
t1$ sudo docker rm -f [コンテナ名]

(t1)停止したものも含めて全てのコンテナの一覧を確認

  • httpdのコンテナはもう残ってない
t1$ sudo docker ps -a

(4) Apache+PHPが入ったDockerコンテナとMySQLが入ったDockerコンテナの2つで簡単なLAMPのWebサイトを起動

ねらい: より実践的なDockerコンテナの活用を体験する。

準備

2枚めのターミナル(t2と呼称。1枚目はt1と呼称)を起動し、両方ともVMにSSHログインしておくこと。

Apache httpd+PHP入りコンテナの起動

(t1)DockerHubからApache httpd+PHP入りのDockerイメージを取得し、取得したイメージからコンテナを作成

  • --nameオプションを付けることで、コンテナに任意の名前を付与できる。今回は「web」にする
t1$ sudo docker run -p 80:80 -d --name web php:apache

(t1)稼働中のコンテナの一覧を確認

  • コンテナ名が「web」になっている
t1$ sudo docker ps

(t1)localhostにWebアクセスしてみる

  • アクセスできるが、403が返ってくる
t1$ curl localhost

(t1)docker execコマンドにより、httpd+PHP入りコンテナ内でbashを起動

  • 今回はホームディレクトリが /var/www/html になっている
t1$ sudo docker exec -it web bash

(t1)コンテナ内でindex.phpファイルを追加

  • エディタがないのでechoコマンドを使用
container# echo "<?php phpinfo(); ?>" > index.php

(t2)localhostにWebアクセスしてみる

  • phpinfo()の出力が得られるはず
t2$ curl localhost

(t1)コンテナから抜ける

container# exit

ここまででわかったこと

  • Dockerイメージを取得できれば、PHP入りのWebサーバもDockerコンテナ内で簡単に起動できる

MySQL入りコンテナの起動

(t1)DockerHubからMySQL入りのDockerイメージを取得し、取得したイメージからコンテナを作成

  • --nameオプションを付けることで、コンテナに任意の名前を付与できる。今回は「mysql」にする
  • -eオプションを使用してMYSQL_ROOT_PASSWORDという環境変数を与え、管理者(root)のパスワードを設定
t1$ sudo docker run -p 3306:3306 -d --name mysql -e MYSQL_ROOT_PASSWORD=admin mysql:5.7

(t1)稼働中のコンテナの一覧を確認

  • コンテナ名が「mysql」になっている
t1$ sudo docker ps

(t1)docker execコマンドにより、MySQL入りコンテナ内でbashを起動

t1$ sudo docker exec -it mysql bash

(t1)コンテナ内で環境変数を確認

  • MYSQL_ROOT_PASSWORDという環境変数が設定されている
container# env

(t1)コンテナ内でMySQLクライアントCLIを起動

container# mysql -u root -padmin

(t1)論理DB一覧を取得

mysql> show databases;

(t1)論理DB、テーブルを作成

  • 後のテスト用PHPスクリプトで使用する
mysql> create database test;
mysql> use test;
mysql> create table test(name VARCHAR(100));
mysql> show tables;
mysql> exit

(t1)コンテナから抜ける

container# exit

ここまででわかったこと

  • Dockerイメージを取得できれば、MySQLもDockerコンテナ内で簡単に起動できる

PHPからMySQLが使えるようにwebコンテナに拡張パッケージをインストール

(t1)docker execコマンドにより、webコンテナ内でbashを起動

t1$ sudo docker exec -it web bash

(t1)外部のaptリポジトリにアクセスできるよう、proxy設定を追加する

  • コピペにミスらないようにご注意を。特にダブルクォート
container# echo "Acquire::http::proxy \"http://172.20.253.21:8080/\";" > /etc/apt/apt.conf.d/99proxy

(t1)外部のaptリポジトリにアクセスできることを確認

container# apt-get update

(t1)MySQLアクセス用のPHP拡張パッケージをインストール

container# docker-php-ext-install pdo_mysql mysqli

(t1)コンテナから抜ける

container# exit

(t1)docker restartコマンドにより、webコンテナを再起動

  • PHP拡張パッケージを有効にするため
t1$ sudo docker restart web

ここまででわかったこと

  • Dockerコンテナ内でproxy設定を適切に行えば、外部リポジトリへのアクセスも可能

テスト用PHPスクリプトをDockerホストからコンテナ内にコピー

(t1)Dockerホスト上でテスト用PHPスクリプト test.php を作成

t1$ vim ./test.php
  • 以下をコピペし、[自ホストIPアドレス]の部分だけ変更して下さい。
<?php
$db = new PDO('mysql:host=[自ホストIPアドレス];dbname=test', 'root', 'admin');
$db->query("INSERT INTO test VALUES('test!')");
$st = $db->query("SELECT * FROM test");
var_dump($st->fetchAll());
?>

(t1)Dockerホストからwebコンテナ内に test.php をコピー

t1$ sudo docker cp ./test.php web:/var/www/html

(t1)webコンテナのtest.phpにWebアクセスしてみる

  • 動くはず
t1$ curl localhost/test.php

(t1)webコンテナのtest.phpに繰り返しWebアクセスしてみる

  • アクセスのたびにDB insertが行われ、レコードが増えてゆくはず
t1$ curl localhost/test.php
t1$ curl localhost/test.php

ここまででわかったこと

  • DockerホストからDockerコンテナ内へのファイルコピーが docker cpコマンドで可能
    • Dockerコンテナ内からDockerホストへのファイルコピーも、もちろん可能です

実行中のDockerコンテナからDockerイメージを保存する

(t1)稼働中のコンテナの一覧を確認

t1$ sudo docker ps

(t1)webコンテナをDockerイメージとして保存する

  • docker commitコマンドを使用する
  • [チーム名]は英数字であればなんでもよい。
t1$ sudo docker commit web [チーム名]/web:v1

(t1)Dockerイメージが保存されたか確認する

  • ちゃんとあるはず
t1$ sudo docker images

(t1)稼働中のコンテナの一覧を確認

  • まだ動いているはず
t1$ sudo docker ps

(t1)webコンテナを停止し削除する

t1$ sudo docker rm -f web

(t1)停止したものも含めて全てのコンテナの一覧を確認

  • webコンテナはもう残ってない
t1$ sudo docker ps -a

(t1)保存したDockerイメージを使用してwebコンテナを復活させる

t1$ sudo docker run -p 80:80 -d --name=web [チーム名]/web:v1

(t1)稼働中のコンテナの一覧を確認

  • 再度動いているはず
t1$ sudo docker ps

(t1)webコンテナのtest.phpにWebアクセスしてみる

  • 動くはず
t1$ curl localhost/test.php

ここまででわかったこと

  • DockerコンテナからDockerイメージの保存がdocker commitコマンドで可能

(5) DockerfileからのDockerイメージのビルドと起動

ねらい: 各チームの今後の課題解決に向けた知識を獲得する。

はじめに

  • docker commitコマンドを用いて、動作中のDockerコンテナに手を加えた結果をDockerイメージとして保存できることがわかった。
  • しかし、そのやりかただと、Dockerイメージの作成方法についてのノウハウが属人化してしまう。
  • Dockerイメージの作成方法をコードとして管理したいので、Dockerfileを使ったDockerイメージのビルドの手順も知っておきたい。

先ほどのMySQLアクセス可能なPHP+Apache httpd入りコンテナをビルドするDockerfile

FROM php:apache

RUN echo "Acquire::http::proxy \"http://172.20.253.21:8080/\";" > /etc/apt/apt.conf.d/99proxy
RUN apt-get update
RUN docker-php-ext-install pdo_mysql mysqli
RUN echo "<?php phpinfo(); ?>" > /var/www/html/index.php
ADD ./test.php /var/www/html

DockerfileからDockerイメージをビルドし、そのイメージからコンテナを起動する

t1$ vim Dockerfile
t1$ sudo docker build -t [チーム名]/web:v2 .
t1$ sudo docker images
t1$ sudo docker rm -f web
t1$ sudo docker run -p 80:80 -d --name=web [チーム名]/web:v2

動作確認する。

t1$ sudo docker ps
t1$ curl localhost
t1$ curl localhost/test.php

ここまででわかったこと

  • DockerfileからDockerイメージのビルドがdocker buildコマンドで可能
  • Dockerfileは、基本的にDockerコンテナ内で手で実行する手順を書き下したものと捉えることができる

【参考】いろんなDockerfile

(6) Dockerコンテナ内での単体テストの実行

ねらい:CI(Continuous Integration、継続的インテグレーション)の導入・拡充に向けたコンテナ技術の応用を考える

はじめに

  • ここまでで、Dockerコンテナ(を含めたコンテナ仮想化)には以下の特長があることがわかった
    • 仮想環境のライフサイクルの操作(作成、起動、停止、削除)が軽い
      • なので、トライアンドエラーを繰り返しやすい
    • ライフサイクルを繰り返してもホストのディスクを汚染しない
      • コンテナの削除を忘れなければ
  • これらは、自動的に反復してテスト実行を繰り返す、CIの実行環境に非常に適した特長
  • 今回はPHPUnitを使った単体テストをDockerコンテナ内で実行してみることを通じて、そのような特長の活かし方を考える

サンプルコードの準備

  • htdocsディレクトリを利用。
  • 単体テストの対象となる本体コードを準備
$ vim htdocs/sample.php
<?php
class Sample{
  protected $value;
 
  public function get_value(){
    return $this->value;
    throw new RuntimeException('Not yet implemented.');
  }
 
  public function set_value($value){
    $this->value = $value * 2;
  }
}
?>
  • 単体テストを実装したテストコードを準備
$ vim htdocs/sample_test.php
<?php
require_once 'sample.php';
 
class SampleTest extends PHPUnit_Framework_TestCase{
 
  public function setUp(){
    $this->sample = new Sample;
  }
 
  public function testGetValue(){
    $this->sample->set_value(20);
    $this->assertEquals(40, $this->sample->get_value());
  }
}
?>

Dockerコンテナ内でのPHPunitの実行

  • htdocsディレクトリをコンテナ内の/appディレクトリにマウントし、テストコードのPHPファイルを指定してPHPunitを起動する。テスト完了したらコンテナを消す
$ sudo docker run --rm -v $PWD/htdocs:/app phpunit/phpunit sample_test.php
  • テストケース1個が通ったとの出力があるはず。ちなみに、終了コードは「0」のはず
$ echo $?
  • テストを故意に失敗させるため、本体コードにバグを仕込む
    • $value * 2$value * 3に変更
$ vim htdocs/sample.php
<?php
class Sample{
  protected $value;
 
  public function get_value(){
    return $this->value;
    throw new RuntimeException('Not yet implemented.');
  }
 
  public function set_value($value){
    $this->value = $value * 3;
  }
}
?>
  • 再度PHPunitを起動
$ sudo docker run --rm -v $PWD/htdocs:/app phpunit/phpunit sample_test.php
  • テストケース1個が失敗した旨の出力があるはず。ちなみに、終了コードは「1」のはず
$ echo $?

自動テストと終了コードの話

テストツールやテストスクリプトの終了コードとして、テスト成功だったら0、テスト失敗だったら0以外とするのは、今後CIツール(Jenkins、Concourseなど)を活用するうえでの基本となる。

PHPunitのDockerイメージをビルドしたDockerfile

https://github.com/JulienBreux/phpunit-docker/blob/master/5.5.0/Dockerfile

  • PHP7.0、PHPUnit5.5.0がインストールされたDockerイメージであることがわかる
  • ENTRYPOINTに/usr/local/bin/phpunitが指定してあるため、(2)でbashを起動したときのようにわざわざ/usr/local/bin/phpunitと打たなくてもphpunitが起動する
  • CMD ["--help"]というのは、docker run時にイメージ名の後に何もつけなければphpunitコマンドに--helpオプションを付けるという意味。今回は sample_test.php を指定したので、CMDの定義がオーバライドされた
  • 最後のWORKDIRが /app なので、phpunit実行時のカレントディレクトリが /app になる。docker run時に-vオプションをつけて /appに htdocs ディレクトリをマウントさせたので、sample_test.php を指定する際にhtdocsディレクトリを指定しなくてもよかった
  • その他詳細は https://hub.docker.com/r/phpunit/phpunit/ を参照

残存するDockerコンテナ、Dockerイメージ

$ sudo docker ps -a
$ sudo docker images
  • PHPunitのDockerイメージは残っているが、コンテナ自体は消えているので、テストが繰り返されてもその都度ディスクを消費するということはない
  • 異なるバージョンのPHPやPHPUnitを使用したい場合は、Dockerfileを改造してdocker buildコマンドを使って別の新たなDockerイメージを作成することになる

(7) Docker Composeによる複数Dockerコンテナの一括操作

ねらい:複数コンテナを組み合わせた開発検証環境の迅速な構築・更新・削除に向けた知識を獲得する

はじめに

  • 複数コンテナから構成されるサービスを一括で操作できると、実行コマンド数が減ってオペミスが減りそう
  • 特にdocker runコマンドは、コンテナ実行条件を指定するための引数が非常に多くなりがちで、仮にスクリプト化したとしても可読性は低いので、コンテナ実行条件を設定ファイルにしたい
  • 上記の問題はDocker Composeという、dockerコマンドをラップするツールを使うと解決できる
  • 今回は、(4)で試した2コンテナでLAMPを動かすという構成を、Docker Composeを使って一括で構築・削除するのをやってみる

Docker Composeのインストール

Docker DomposeはGo言語で書かれているコマンドラインツールであり、ライブラリが静的にリンクされたバイナリがGitHubで配布されているので、それをそのままダウンロードして使用する。

t1$ sudo curl -vLx http://172.20.253.21:8080 https://github.com/docker/compose/releases/download/1.8.1/docker-compose-`uname -s`-`uname -m` > ./docker-compose
t1$ chmod +x ./docker-compose
t1$ sudo mv ./docker-compose /usr/bin/
t1$ docker-compose -v

(4)でMySQLをどう動かしたか【復習】

t1$ sudo docker run -p 3306:3306 -d --name mysql -e MYSQL_ROOT_PASSWORD=admin mysql:5.7
t1$ sudo docker exec -it mysql bash
container# mysql -u root -padmin
mysql> create database test;
mysql> create table test.test(name VARCHAR(100));
mysql> exit
  • mysqlコンテナにbashで入ってmysqlコマンド起動するが、その際に環境変数で与えられるべきrootのパスワードを指定する必要がある。 → https://hub.docker.com/_/mysql/ によると、create database testの代わりにdocker runのコマンドオプションとして-e MYSQL_DATABASE=testを付け加えることで、コンテナ起動時に自動的に create databaseしてくれる

(5)でApache+PHPをどう動かしたか【復習】

t1$ sudo docker build -t ojiri/web:v2 .
t1$ sudo docker run -p 80:80 -d --name=web ojiri/web:v2
  • create table test.test の代わりは、test.phpを改造して対応することにする。
  • ついでに、PHPからMySQLへの接続先IPアドレスも環境変数から読み込むようにしておく。

準備

(t1)test.php を次のように改造する

t1$ vim test.php
<?php
$db = new PDO('mysql:host='.$_ENV['MYSQL_PORT_3306_TCP_ADDR'].';dbname=test', 'root', 'admin');
$db->query("CREATE TABLE IF NOT EXISTS test (name VARCHAR(100))");
$db->query("INSERT INTO test VALUES('test!')");
$st = $db->query("SELECT * FROM test");
var_dump($st->fetchAll());
?>

(t1)Docker Compose用の設定ファイル docker-compose.yml を用意する

t1$ vim docker-compose.yml
version: '2'

services:
  mysql:
    image: mysql:5.7
    ports:
      - "3306:3306"
    environment:
      MYSQL_ROOT_PASSWORD: admin
      MYSQL_DATABASE: test
  web:
    build: .
    ports:
      - "80:80"
    environment:
      MYSQL_PORT_3306_TCP_ADDR: [自ホストのIPアドレス]

Docker Composeで2コンテナを一括起動

(t1)まずはコンテナが何もないことを確認

t1$ sudo docker ps -a

(t1)一括起動

  • webコンテナのイメージのビルドもしたいので--buildオプションをつける
  • ログがゾロゾロと流れ、フォアグラウンドで起動完了する
t1$ sudo docker-compose up --build

(t2)動作確認

  • 問題なく動くはず。t1でアクセスをログも確認
t2$ curl localhost
t2$ curl localhost/test.php

(t2)コンテナの状態を確認

  • 2コンテナちゃんと動いているはず
  • docker-compose.ymlが入ってるディレクトリ名と、docker-compose.ymlで記述したサービス名を使って、勝手にコンテナ名が振られているはず
t2$ sudo docker ps

(t2)Docker Composeでコンテナの状態を確認

  • 出力が簡素化されているが、確認できる
t2$ sudo docker-compose ps

(t1)2コンテナを強制停止、削除

  • t1で^C
  • コンテナが残るので、Docker Composeで一括削除
t1$ sudo docker ps -a
t1$ sudo docker-compose rm

Docker Composeで2コンテナをバックグラウンドで一括起動

  • Docker Composeにも-dオプションがある
t1$ sudo docker-compose up -d
t1$ sudo docker ps

(t1)動作確認する

t1$ curl localhost

(t1)ログを確認する

t1$ sudo docker-compose logs

(t1)ログをtail -f的に流す

  • 止めるのはt1で^C
t1$ sudo docker-compose logs -f
t2$ curl localhost

ネットワークについて

(t2)ipコマンドでネットワークインタフェースの状況を確認

  • 新しいbridgeが増えていて、172.18.0.1のIPアドレスが振られている
t2$ ip a

(t2)docker network lsコマンドでネットワークの状況を確認

  • 一番下になんか増えてる
t2$ sudo docker network ls

(t2)docker network inspectコマンドで増えたネットワークの詳細を確認する

  • 詳しい情報がとれる。今回起動した2コンテナのIPアドレスも情報に含まれている
t2$ sudo docker network inspect [ネットワーク名]

Docker Composeでバックグラウンドで動くコンテナを一括停止・一括削除

(t1)一括停止・一括削除

  • ネットワークも削除される
t1$ sudo docker-compose down

(t1)コンテナ、ネットワークの状態確認

  • いずれも消えてるはず
t1$ sudo docker ps -a
t1$ sudo docker network ls

環境依存性を極力なくすための更なる改善

  • MySQLへの接続先IPアドレスを明示的にdocker-compose.ymlに書かなければならないのをなんとかする

(t1)docker-compose.ymlを編集

  • webコンテナのMYSQL_PORT_3306_TCP_ADDR環境変数設定を削除する代わりに、linksという設定を追加
  • mysqlコンテナのports設定も削除
t1$ vim docker-compose.yml
version: '2'

services:
  mysql:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: admin
      MYSQL_DATABASE: test
  web:
    build: .
    ports:
      - "80:80"
    links:
      - mysql

(t1)test.phpの編集

  • test.phpの2行目でMYSQL_PORT_3306_TCP_ADDR環境変数からMySQL接続先IPアドレスを取っていたところを、単に「mysql」に変更
t1$ vim test.php
<?php
$db = new PDO('mysql:host=mysql;dbname=test', 'root', 'admin');
$db->query("CREATE TABLE IF NOT EXISTS test (name VARCHAR(100))");
$db->query("INSERT INTO test VALUES('test!')");
$st = $db->query("SELECT * FROM test");
var_dump($st->fetchAll());
?>

(t1)一括起動

  • webコンテナのイメージの再ビルドもしたいので--buildオプションをつける
t1$ sudo docker-compose up -d --build

(t1)動作確認

  • ちゃんと動作するはず
t1$ curl localhost/test.php

(t1)webコンテナ内で「mysql」がちゃんと名前解決できているか確認

  • Docker Composeだと、-itオプションつけなくてもコンテナ内でbashのコマンドプロンプトが取れる
  • pingがちゃんと返ってくるはず。これはDocker Composeが提供する機構により、ホスト名→IPアドレスの名前解決ができているから
  • 解決されたIPアドレスは追加された内部ネットワークのものになっている。このため、1つのdocker-compose.ymlに閉じていれば、ポートを外部に開放する必要はなくなる
t1$ sudo docker-compose exec web bash
container# ping mysql

後かたづけ

t1で^Cしたあと、以下を実行

  • docker-compose up --buildしてイメージの再ビルドが走ると、ゴミイメージが残りがちなので、要らないものは消す
t1$ sudo docker-compose down
t1$ sudo docker images

(8) プライベートDockerレジストリの立て方と使い方

ねらい:Dockerをチーム内コラボに活用するための知識を獲得する

はじめに

https://speakerdeck.com/ozzozz/dockerimezizhi-kichang-falsejian を説明

プライベートDockerレジストリの起動

$ sudo docker run -d -p 5000:5000 --name=registry registry:2

プライベートDockerレジストリへのDockerイメージのpush

  • [チーム名]はなんでもよい
$ sudo docker tag [チーム名]/web:v2 localhost:5000/[チーム名]/web:v2
$ sudo docker push localhost:5000/[チーム名]/web:v2

push済みの手元のDockerイメージを削除する

$ sudo docker images
$ sudo docker rmi -f [[チーム名]/web:v2のイメージID]

プライベートDockerレジストリからDockerイメージのpull

$ sudo docker pull localhost:5000/[チーム名]/web:v2
$ $ sudo docker images

【説明のみ】CentOS7の場合のDockerエンジンのinsecure registry設定追加

$ sudo vim /etc/sysconfig/docker

以下の行を追加

INSECURE_REGISTRY='--insecure-registry=[プライベートDockerレジストリのIPアドレス]'

Dockerエンジンを再起動

$ sudo systemctl restart docker

【参考】harborについて

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment