タグ: Docker

自己署名証明書を生成してくれるdockerコンテナOMGWTFSSLを試す

ローカルでの開発などでLet’s Encryptを使用できないときに、従来の方法で自己署名証明書が必要な際、このOMGWTFSSLを使って証明書を生成すると、プライベート認証局の証明書とそれで署名したサーバ証明書を得ることができます。

プライベート認証局の証明書をブラウザに登録し、サーバ証明書をApache,NGIXなどに設定すると、例外設定せずにhttpsでアクセスすることができます。

paulczar/omgwtfssl: SSL certificate generation for developers who don’t TLS good

OpenSSLを使った従来の方法

OMGWTFSSLを使った方法

前提

  • OSはUbuntu 18.04
  • WEBサーバとしてApacheを使用
  • Dockerはインストール済み
  • foo.example.jpというドメイン名でアクセスできるように設定済み

証明書の生成とインストール

  1. OMGWTFSSLコンテナを実行
    $ docker run -e SSL_SUBJECT="foo.example.jp" -v /tmp/certs:/certs  paulczar/omgwtfssl
    ----------------------------
    | OMGWTFSSL Cert Generator |
    ----------------------------
    
    --> Certificate Authority
    ====> Generating new CA key ca-key.pem
    Generating RSA private key, 2048 bit long modulus
    ....................................................+++
    .+++
    e is 65537 (0x10001)
    ====> Generating new CA Certificate ca.pem
    ====> Generating new config file openssl.cnf
    ====> Generating new SSL KEY key.pem
    Generating RSA private key, 2048 bit long modulus
    ....................................................................................................................................................................................................................+++
    ....................................................+++
    e is 65537 (0x10001)
    ====> Generating new SSL CSR key.csr
    ====> Generating new SSL CERT cert.pem
    Signature ok
    subject=/CN=foo.example.jp
    Getting CA Private Key
    ====> Complete
    keys can be found in volume mapped to /certs
    
    ====> Output results as YAML
    ---
    ca_key: |
      -----BEGIN RSA PRIVATE KEY-----
      :
      :
      :
      -----END RSA PRIVATE KEY-----
    
    ca_crt: |
      -----BEGIN CERTIFICATE-----
      :
      :
      :
      -----END CERTIFICATE-----
    
    ssl_key: |
      -----BEGIN RSA PRIVATE KEY-----
      :
      :
      :
      -----END RSA PRIVATE KEY-----
    
    ssl_csr: |
      -----BEGIN CERTIFICATE REQUEST-----
      :
      :
      :
      -----END CERTIFICATE REQUEST-----
    
    ssl_crt: |
      -----BEGIN CERTIFICATE-----
      :
      :
      :
      -----END CERTIFICATE-----
    
  2. 生成されたファイルの確認
    $ ls -l /tmp/certs
    total 32
    -rw-r--r-- 1 root root 1679 Apr 15 07:51 ca-key.pem
    -rw-r--r-- 1 root root  973 Apr 15 07:51 ca.pem
    -rw-r--r-- 1 root root   17 Apr 15 07:51 ca.srl
    -rw-r--r-- 1 root root 1066 Apr 15 07:51 cert.pem
    -rw-r--r-- 1 root root  997 Apr 15 07:51 key.csr
    -rw-r--r-- 1 root root 1675 Apr 15 07:51 key.pem
    -rw-r--r-- 1 root root  241 Apr 15 07:51 openssl.cnf
    -rw-r--r-- 1 root root 3791 Apr 15 07:51 secret.yaml
    

    プライベート認証局の証明書と秘密鍵、サーバ証明書とその秘密鍵などが生成されている

  3. サーバ証明書を移動

    ApacheのデフォルトSSL設定の/etc/apache2/sites-available/default-ssl.confにある、SSLCertificateFileSSLCertificateKeyFileのパスがそれぞれ、/etc/ssl/certs//etc/ssl/private/にあるので、それにならってファイルを移動

    $ sudo mv /tmp/certs/cert.pem /etc/ssl/certs/
    $ sudo mv /tmp/certs/key.pem /etc/ssl/private/
    
  4. サイトの追加

    Apacheにfoo.example.jp用の設定を追加する

    $ sudo vi /etc/apache2/sites-available/999-foo.conf
    
    <VirtualHost *:443>
        ServerName foo.example.jp
    
        DocumentRoot /var/www/html
    
        SSLEngine On
        SSLCertificateFile      /etc/ssl/certs/cert.pem
        SSLCertificateKeyFile   /etc/ssl/private/key.pem
    </VirtualHost>
    

    必要に応じてSSLOptionsなども追加する

  5. ApacheのSSLモジュール有効化

    $ sudo a2enmod ssl
    
  6. サイト設定を有効化
    $ sudo a2ensite 999-foo
    
  7. Apache再起動
    $ sudo systemctl restart apache2
    

ブラウザに認証局証明書のインストール

https://foo.example.jp にアクセスすると、証明書の発行元不明エラーが出るのでブラウザに認証局証明書をインストールする

  • Firefox
    firefoxでfoo.example.jpにアクセス
    SSL警告内容詳細
  • Chrome
    chromeでfoo.example.jpにアクセス

Firefoxの場合

  1. Firefoxオプションを開く

    オプション→プライバシーとセキュリティー→証明書

    firefox証明書オプション

  2. 証明書マネージャーを開く

    証明書を表示ボタンを押下して証明書マネージャーを開く

    firefox証明書マネージャー

  3. 認証局証明書をインポート

    1. 認証局証明書タブを選択して、その下のインポートボタンを押下

    2. /tmp/certs/ca.pemをダウンロードやファイル移動などで取得し、それを選択

    3. 証明書のインポートダイアログが表示されるので、この認証局によるウェブサイトの識別を信頼するにチェックしてOKボタンを押下

      認証局証明書のインポート

    正常にインポートできていれば、認証局証明書のリストにtest-caが追加されている

    認証局証明書のインポート後

  4. 再度Firefoxで https://foo.example.jp にアクセス

    認証局証明書のインストール後

Chrome(Windows)の場合

Windows版のChromeはインターネットオプションにある証明書を使用しているため、Chromeの設定から開く方法と、コントロールパネルから開く方法がある

  1. 証明書のインポートウィザートでインポートする証明書を選択する

    その際、デフォルトの拡張子がX.509証明書(*.cer, *.crt)となっていることがあるため、拡張子がpemのca.pemが表示されない

    ファイル名横のリストをすべてのファイル(*.*)に変更することで選択可能になる

    証明書インポートウィザード1

  2. インポートする先(証明書ストア)は信頼されたルート証明機関を選択する

    証明書インポートウィザード2

  3. セキュリティ警告ダイアログが表示されるのでOKを押下

  4. 正常にインポートできていれば、信頼されたルート証明機関のリストにtest-caが追加されている

    Chromeでルート証明書インポート後

  5. https://foo.example.jp にChromeでアクセス

    Chromeでルート証明書インストール後

    Firefoxと違ってエラー(NET::ERR_CERT_COMMON_NAME_INVALID)が表示されたまま

    Chrome58で、HTTPSの自己証明書が NET::ERR_CERT_COMMON_NAME_INVALID になる場合の対応より、サーバ証明書にX509v3 Subject Alternative Nameを含むようにしないといけない

  6. オプションを追加してサーバ証明書を再度生成する

    $ docker run -e SSL_SUBJECT="foo.example.jp" -e SSL_DNS="foo.example.jp" -v /tmp/certs:/certs  paulczar/omgwtfssl
    ----------------------------
    | OMGWTFSSL Cert Generator |
    ----------------------------
    
    --> Certificate Authority
    ====> Using existing CA Key ca-key.pem
    ====> Using existing CA Certificate ca.pem
    ====> Generating new config file openssl.cnf
    ====> Generating new SSL KEY key.pem
    Generating RSA private key, 2048 bit long modulus
    .........................................................+++
    ...................................................+++
    e is 65537 (0x10001)
    ====> Generating new SSL CSR key.csr
    ====> Generating new SSL CERT cert.pem
    Signature ok
    subject=/CN=foo.example.jp
    Getting CA Private Key
    ====> Complete
    keys can be found in volume mapped to /certs
    
    ====> Output results as YAML
    ---
    ca_key: |
      -----BEGIN RSA PRIVATE KEY-----
       :
       :
      -----END RSA PRIVATE KEY-----
    
    ca_crt: |
      -----BEGIN CERTIFICATE-----
       :
       :
       :
      -----END CERTIFICATE-----
    
    ssl_key: |
      -----BEGIN RSA PRIVATE KEY-----
       :
       :
       :
      -----END RSA PRIVATE KEY-----
    
    ssl_csr: |
      -----BEGIN CERTIFICATE REQUEST-----
       :
       :
       :
      -----END CERTIFICATE REQUEST-----
    
    ssl_crt: |
      -----BEGIN CERTIFICATE-----
       :
       :
       :
      -----END CERTIFICATE-----
    

    生成されたサーバ証明書を確認する

    $ openssl x509 -in /tmp/certs/cert.pem -text
    Certificate:
         :
         :
         :
            Issuer: CN = test-ca
         :
         :
         :
            Subject: CN = foo.example.jp
         :
         :
         :
            X509v3 extensions:
                X509v3 Basic Constraints:
                    CA:FALSE
                X509v3 Key Usage:
                    Digital Signature, Non Repudiation, Key Encipherment
                X509v3 Extended Key Usage:
                    TLS Web Client Authentication, TLS Web Server Authentication
                X509v3 Subject Alternative Name:
                    DNS:foo.example.jp, DNS:foo.example.jp
         :
         :
         :
    

    ※ paulczar/omgwtfsslが使用しているベースコンテナのalpineの仕様変更のためか、SSL_DNSオプションを追加しても「X509v3 Subject Alternative Name」が出力されなくなっています。paulczar/omgwtfsslからフォークされたstakater/ssl-certs-generator:1.0を使用すると出力されました(2019/05/09)

    $ docker run -e SSL_SUBJECT="foo.example.jp" -e SSL_DNS="foo.example.jp" -v /tmp/certs:/certs stakater/ssl-certs-generator:1.0
    
  7. 認証局の証明書と鍵が生成済みの場合、サーバ証明書への署名はそれを利用するので認証局証明書は再度インポートする必要はない
    ====> Using existing CA Key ca-key.pem
    ====> Using existing CA Certificate ca.pem
    
  8. 生成したサーバ証明書を上書きする
    $ sudo cp /tmp/certs/cert.pem /etc/ssl/certs/
    $ sudo cp /tmp/certs/key.pem /etc/ssl/private/
    $ sudo systemctl reload apache2
    
  9. Chromeでアクセスして確認

    証明書更新後にChromeでアクセス

vagrantのshell provisionerにrun: “always”オプションを使うことでvagrant upするたびにdocker-composeを実行させる

開発環境をVagrantDocker(docker-compose)で構築したのですが、毎回コンテナを起動するために

  1. vagrant up
  2. SSHでゲストOSにログイン
  3. ゲストOS上でdocker-compose up

という手順を踏んでいました。

VagrantとDockerを使ったPHP実行環境を生成するサービス:PHPDocker.io – PHP and Docker development environment generator で生成されるVagrantfileを見ると、以下の方法でdocker-composeを起動させていました。

    # Bring up containers
    config.vm.provision "shell", run: "always", inline: "cd /home/docker/sample/phpdocker && docker-compose up -d 1>&2"

runオプションはProvisionerの共通オプションで、デフォルトでは最初のvagrant upでのみprovisionerが実行されますが、alwaysを指定することで、vagrant up, vagrant reloadのたびに実行されるようになります。

実際には以下のようにして使っています。

冒頭の3.のdocker-compose upはserver.shというスクリプトに書いてserver.sh upとして使っていました。

#!/bin/bash -eu

PWD=$(cd $(dirname $0); pwd)
PROJNAME=sampleproject

ARGS=
if [ $# -eq 0 ];then
    ARGS="up -d"
else
    ARGS=$@
fi

cd $PWD && docker-compose -p $PROJNAME $ARGS

Vagrantfileに以下の内容を追加しました。

  config.vm.provision "shell", run: "always", inline: "[ -f /home/vagrant/sampleproject/server.sh ] && /home/vagrant/sampleproject/server.sh"

プライベートGyazoサービスの構築

スクリーンショットをサーバにアップロードしてURLで共有できるGyazoというサービスがあります。
このOpen Source ClientのコードがGitHubにあるので、これでプライベートGyazoサービスを構築します。

今回の構成は

  • クライアント
    • Windows 10
    • gyazo/Gyazowinをもとにアップロード先URLを変更したものを作成
  • サーバー
    • Ubuntu 14.04
    • gyazo/GyazoのServerにあるRubyスクリプトを使う
    • Gyazoサーバプログラムはインストールから動作までDocker内で行う
    • アップロードされた画像、プログラム内部で利用するデータベースのような動的生成されるデータはdockerのマウント機能を使ってサーバ上に保存する

という形にしました。

サーバー側

必要なもの

  • Docker (記事執筆時に利用したのは1.7.1)
  • サービス用のドメイン

ドメインは http://~ でアクセスできるようにDNSで関連付けておいてください。

今回はgyazo.example.jpとしました。

  1. Gyazo用のディレクトリを作成
    mkdir gyazo
    cd gyazo
    
  2. Dockerfileを作成

    11行目の[SERVERDOMAIN]はサービス用のドメインに置き換えてください。

    例)

    RUN sed -i -e "s/gyazo.com/gyazo.example.jp\/data/" /usr/local/apache2/htdocs/upload.cgi
    
  3. イメージを作成

    docker buildでイメージを作成します。イメージにgyazoという名前を付けます。

    docker build --rm -t gyazo .
    
  4. gyazoイメージからgyazoという名前でコンテナを起動する
    docker run -d -p 80:80 --name gyazo -v `pwd`/data:/usr/local/apache2/htdocs/data -v `pwd`/db:/usr/local/apache2/htdocs/db gyazo
    
  5. http://gyazo.example.jp にアクセスして動作を確認

    「It’s works」がでればOK。

    Itsworks

クライアント側

必要なもの

  • Visual Studio 2015 Community (本記事ではインストール時に既定でインストールしています)
  1. ソースコードの取得

    gyazo/Gyazowinからソースコードをクローン、またはダウンロードします。今回はZIPでダウンロードしました。

  2. ZIPを展開して中にある、「gyazowin.sln」をダブルクリックで開く

    gyazowin.slnはVisual Studio 2015よりも古いフォーマットのファイルのため、アップグレード警告が表示されます。そのままアップグレードします。
    またVC++の開発環境がない場合、ここで追加インストールのウィザードが始まるのでインストールしたのち、gyazowin.slnを開きなおします。

    Upgrade_1Upgrade_2Upgrade_3

    InstallVC_2
    InstallVC_3

  3. コードの編集

    gyazowinはスクリーンショット画像をgyazoに送信する機能を持っていますが、送信先はソースコードにハードコーティングされているため、そこを今回用意したサーバへ変更します。

    gyazowin.cppの794行目のupload.gyazo.comgyazo.example.jpに変更します。

    Diff_gyazowin

  4. ビルドの実行

    1. メニュー「ビルド」の下にあるソリューション構成を「Debug」から「Release」に変更する
      Build Settings
    2. メニュー「ビルド」→「ソリューションのビルド」でビルドを実行

    正常に終了すれば、Releaseディレクトリが作成され、中にgyazowin.exeが生成されます。

  5. 実行確認

    1. gyazowin.exeを実行する
    2. カーソルが十字になる
    3. スクリーンショットを撮りたい範囲の左上をクリックしたまま、右下までドラッグ
    4. 指を話すと、スクリーンショットがgyazo.example.jpにアップロードされたのち、アップロード先URLで既定のブラウザが起動される。
      Gyazowinフォルダのエクスプローラをスクリーンショットしたもの
    5. サーバのdockerを起動したディレクトリの直下にdataディレクトリが作成され、そこにアップロードされた画像が配置する
      server_upload_images

備考

参考

Dockerコンテナをホスト名で参照できるDNSサーバコンテナ「dnsdock」を使う

ホストとなるサーバ上にA,Bの2つのコンテナが稼働しているとき、片方のコンテナからもう片方のコンテナを参照したいときは、簡単な方法はdockerコマンドの–linkオプションを使います。

これとは別に、ホスト名を使うことにより各コンテナを参照することができるコンテナがdnsdockです。
これを使うと–linkオプションなしで相互のコンテナにアクセスできます。
dnsdockはskydockをもとに簡易化したものです。

※コンテナ同士のやり取りは前提としてDockerデーモンのiccパラメータがtrue(デフォルト)で、アクセスされたいコンテナがexposeでポートをさらしている必要があります。

使用例

前提

  • ホストのOSはubuntu14.04
  • ホストのプロンプトを(host),コンテナのプロンプトを(コンテナ名)とする
  • rootは#、ユーザーは$とする

今回は、php(php:5-apache)とmysql(mysql:5.6)のコンテナを使い、

  • ホストからmysqlコマンドでmysqlコンテナにアクセス
  • ホストからcurlコマンドでphpコンテナにアクセス
  • phpコンテナからでmysqlコンテナにアクセス

をしてみます。

1.ホスト側の設定(要root)

Dockerのネットワークを172.17.0.1/24にして、コンテナが参照するDNSを172.17.0.1にする。

  • Dockerデーモンの起動オプションにbipとdnsを加えて、サービスを再起動

    /etc/default/docker

    DOCKER_OPTS="--bip=172.17.0.1/24 --dns=172.17.0.1 --dns=8.8.8.8"
    

    ※各コンテナは172.17.0.1をデフォルトのDNSとしてdnsdockを通して名前解決を行うが、dnsdockが停止していると名前解決が行えないので予備も設定しておく

    (host)# service docker restart
    
  • ネームサーバに172.17.0.1を/etc/resolv.confのnameserverの先頭に加える。直接加えると再起動時に消えるので、resolvconfを利用する

    /etc/resolvconf/resolv.conf.d/head

    nameserver 172.17.0.1
    
    (host)# resolvconf -u
    

2.作業領域と確認用コードの作成

(host)$ mkdir work
(host)$ cd work
(host)$ mkdir src
(host)$ echo '<?php echo "Hello World\n"; ' > src/index.php

3.dnsdockコンテナの起動

(host)$ docker run -d --name dnsdock -v /var/run/docker.sock:/var/run/docker.sock -p 172.17.0.1:53:53/udp aacebedo/dnsdock:latest-amd64

(追記 2016/10/14)Githubのページがtonistiigi/dnsdockからaacebedo/dnsdockに変更されたのに伴ってコンテナイメージをaacebedo/dnsdock、バージョンをDocker Hubに従ってlatest-amd64に変更しました。
(追記 2016/8/24)issueがcloseされ、clouddynamics/dnsdockがなくなりました
※dnsdockの本家は tonistiigi/dnsdock だが、ホストからアクセスする際にバグがあり、対応されたものが clouddynamics/dnsdock にあるためコンテナイメージはそちらを利用しています。
参考:dig dnsdocker.docker works, curl dnsdocker.docker doesn’t · Issue #34 · tonistiigi/dnsdock

  • digコマンドで名前が解決できるか確認

    (host)$ dig *.docker

    ; <> DiG 9.9.5-3ubuntu0.6-Ubuntu <> *.docker
    ;; global options: +cmd
    ;; Got answer:
    ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 24923
    ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
    
    ;; QUESTION SECTION:
    ;*.docker.                      IN      A
    
    ;; ANSWER SECTION:
    *.docker.               0       IN      A       172.17.42.2
    
    ;; Query time: 1 msec
    ;; SERVER: 172.17.42.1#53(172.17.42.1)
    ;; WHEN: Thu Jan 14 20:12:05 JST 2016
    ;; MSG SIZE  rcvd: 50
    

4.MySQLコンテナとPHPコンテナの起動

(host)$ docker run -d --name some-mysql -e MYSQL_ROOT_PASSWORD=mysqlpass mysql:5.6
(host)$ docker run -d --name some-php -v $(pwd)/src:/var/www/html -p 80:80 php:5-apache
  1. 現在起動中のコンテナの確認

    (host)$ docker ps

    CONTAINER ID        IMAGE                   COMMAND                  CREATED             STATUS              PORTS                    NAMES
    d70f295f19fb        php:5-apache            "apache2-foreground"     36 seconds ago      Up 34 seconds       0.0.0.0:80->80/tcp       some-php
    2b1323183779        mysql:5.6               "/entrypoint.sh mysql"   3 minutes ago       Up 3 minutes        3306/tcp                 some-mysql
    7258ecfbfd04        tonistiigi/dnsdock   "/go/bin/dnsdock"        7 minutes ago       Up 7 minutes        172.17.42.1:53->53/udp   dnsdock
    
  2. 起動したコンテナの名前解決を確認

    ホスト名は<anything>.<container-name>.<image-name>.<environment>.<domain>というルールになっており、デフォルトだと<domain>はdocker、<environment>は空文字列のため、<container-name>.<image-name>.dockerでアクセスできる。

    MySQLコンテナはmysqlイメージを使ってsome-mysqlという名前で起動したため、ホスト名はsome-mysql.mysql.docker

    (host)$ dig some-mysql.mysql.docker

    ; <> DiG 9.9.5-3ubuntu0.6-Ubuntu <> some-mysql.mysql.docker
    ;; global options: +cmd
    ;; Got answer:
    ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 14084
    ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
    
    ;; QUESTION SECTION:
    ;some-mysql.mysql.docker.       IN      A
    
    ;; ANSWER SECTION:
    some-mysql.mysql.docker. 0      IN      A       172.17.42.3
    
    ;; Query time: 0 msec
    ;; SERVER: 172.17.42.1#53(172.17.42.1)
    ;; WHEN: Thu Jan 14 20:20:11 JST 2016
    ;; MSG SIZE  rcvd: 80
    

    同様にPHPコンテナはphpイメージを使って、some-phpという名前で起動したのでホスト名はsome-php.php.docker

    (host)$ dig some-php.php.docker

    ; <> DiG 9.9.5-3ubuntu0.6-Ubuntu <> some-php.php.docker
    ;; global options: +cmd
    ;; Got answer:
    ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 30088
    ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
    
    ;; QUESTION SECTION:
    ;some-php.php.docker.           IN      A
    
    ;; ANSWER SECTION:
    some-php.php.docker.    0       IN      A       172.17.42.4
    
    ;; Query time: 0 msec
    ;; SERVER: 172.17.42.1#53(172.17.42.1)
    ;; WHEN: Thu Jan 14 20:21:15 JST 2016
    ;; MSG SIZE  rcvd: 72
    

5.ホストからPHPコンテナにホスト名でアクセスしてみる

(host)$ curl http://some-php.php.docker/index.php

Hello World

6.ホストからmysqlコマンドでMySQLコンテナにホスト名でアクセスしてみる

mysqlコマンドはあらかじめインストールしておく必要があります。
(ubuntu14.04ならapt-get install mysql-client-core-5.6)

(host)$ mysql -h some-mysql.mysql.docker -u root --password=mysqlpass

7.phpコンテナからmysqlにアクセス

7.1 PHPコンテナにPDOとMySQLのモジュールを組み込む

php:5-apacheにはPDOとMySQLのモジュールが組み込まれていないので、組み込む必要がある。

方法1 モジュール入りカスタムイメージを使う
  1. Dockerfileを作成する

    Dockerfile

    FROM php:5-apache
    RUN docker-php-ext-install pdo pdo_mysql
    
  2. イメージを作成する
    (host)$ docker build -t php:custom  .
    
  3. 動かしているsome-phpコンテナを終了させて、代わりにカスタムイメージで起動する
    (host)$ docker stop some-php
    (host)$ docker rm some-php
    (host)$ docker run -d --name some-php -v $(pwd)/src:/var/www/html -p 80:80 php:custom
    
方法2 現在動いているコンテナにモジュールをインストールする
  1. some-phpコンテナに入る
    (host)$ docker exec -it some-php bash
    
  2. モジュールをインストールする
    (some-php)$ docker-php-ext-install pdo pdo_mysql
    (some-php)$ exit
    
  3. コンテナ再起動
    (host)$ docker restart some-php
    

7.2 index.phpをPDOを使ってMySQLにアクセスするプログラムに書き換える

src/index.php

<?php

    $pdo = new PDO('mysql:host=some-mysql.mysql.docker;dbname=mysql', 'root', 'mysqlpass');
    $sth = $pdo->query("show tables");
    $sth->execute();
    $result = $sth->fetchAll();
    print_r($result);

7.3 ホストからアクセス

(host)$ curl http://some-php.php.docker/index.php

Array
(
    [0] => Array
        (
            [Tables_in_mysql] => columns_priv
            [0] => columns_priv
        )

    [1] => Array
        (
            [Tables_in_mysql] => db
            [0] => db
        )
     :
     :
     :

WEBインタフェース

dnsdockにはRESTインタフェースを持っているので、curlなどで問い合わせやデータの更新ができます。

tonistiigi/dnsdock#HTTP Server

整形にjqコマンドを使用しています。

コンテナの一覧

curl -s http://dnsdock.docker/services|jq .

{
  "672963efeef98b3c473f87f7ede66e51f661cc6f5f28075c79ec44c633d2a564": {
    "Aliases": [],
    "Ttl": -1,
    "Ip": "172.17.42.2",
    "Image": "dnsdock",
    "Name": "dnsdock"
  },
  "2d17b46d944919797ed4f62d9c763e240cf2ed511573f22b438b07ba3239f77b": {
    "Aliases": [],
    "Ttl": -1,
    "Ip": "172.17.42.4",
    "Image": "php",
    "Name": "some-php"
  },
  "263c0325f15f0f4c95f68f1975df5a50104ca766529c772d29d9a1357b8fab17": {
    "Aliases": [],
    "Ttl": -1,
    "Ip": "172.17.42.3",
    "Image": "mysql",
    "Name": "some-mysql"
  }
}

参考

(追記 2016/10/14)
記事執筆時はアドレスとして172.17.42.1を使用していましたが、

ERROR: for dnsdock  Cannot start service dnsdock: driver failed programming external connectivity

というエラーが出るようになったため、下記及びaacebedo/dnsdockを参考に172.17.0.1に変更したらうまく動作するようになりました

1.9: Default docker0 IP is not 172.17.42.1 for new installs · Issue #17305 · docker/docker

DockerのOfficial imageのMySQLを使って初回起動時にカスタムSQLを実行させる

DockerのOfficial RepositoriesMySQLを使って、初回のDBデータ作成時に、カスタムSQLを実行する。

MySQLイメージは起動時にMySQLデータディレクトリ(/var/lib/mysql)の中身がない場合、mysql_install_dbなどが実行されます。その処理の中で、 /docker-entrypoint-initdb.dディレクトリにシェルスクリプトや、SQLが存在する場合、それを実行してくれる仕組みがあります。

サンプル

サンプルとして、Docker Composeを使って、PHPMyAdminとMySQLサーバを構築してみます。
その際、初回起動時に2つのデータベースと1つのテーブルを作成します。
うち1つは環境変数MYSQL_DATABASEをセットすることによって自動的に作成されるデータベース、それ以外がdocker-entrypoint-initdb.dによるものです。

ローカルにinitdb.dフォルダを作成し、それをMySQLイメージ中のdocker-entrypoint-initdb.dにマウントさせます。

initdb.dフォルダには、create_another_db.shとcreate_table_in_exampledb.sqlを置いておきます。

docker-compose upでMySQLが立ち上がると、最後にそれらのファイルが実行されます。

create_another_db.shはDBを新規作成します。シェルなので、環境変数を参照できます。
MySQLへの流し込み方法は、docker-entrypoint.shを参考にパスワードを追記した形です。

create_table_in_exampledb.sqlは環境変数MYSQL_DATABASEによって既に作成されているDBに対してテーブルを作成します。

全部入りはこちら→mistymagich/docker-mysql-sample

参考

Docker MySQLイメージのクエリログ(General Query Log)出力

mysql Repository | Docker Hub Registry – Repositories of Docker Imagesのクエリログを見る方法

方法1.クエリログをファイル出力して、ログディレクトリをデータボリュームでマウント

  1. log.cnf(クエリログを出力するように設定したMySQLの設定ファイル)とDockerfileを作成する

    log.cnf

    [mysqld]
    log_output=FILE
    general_log=1
    general_log_file=/var/log/mysql/query.log
    

    Dockerfile

    FROM mysql
    ADD log.cnf /etc/mysql/conf.d/log.cnf
    RUN chmod 644 /etc/mysql/conf.d/log.cnf
    
  2. ビルド
    $ docker build -t mysql_custom .
    
  3. ログディレクトリを作成
    $ mkdir mysqllog
    
  4. Docker側のMySQLが書き込みできるように権限を与える
    $ chmod 777 mysqllog
    
  5. Docker実行
    $ docker run -e MYSQL_ROOT_PASSWORD=my-secret-pw -v `pwd`/mysqllog:/var/log/mysql -p "3306:3306" -d mysql_custom
    
  6. クエリログの参照
    $ sudo tail -f mysqllog/query.log
    
  7. 上記出力したまま、別端末でMySQLサーバにログインしてクエリを発行し、記録していることを確認
    $ MYSQL_PWD=my-secret-pw mysql -u root -h 127.0.0.1 -P 3306
    mysql> show databases;
    +--------------------+
    | Database           |
    +--------------------+
    | information_schema |
    | mysql              |
    | performance_schema |
    +--------------------+
    3 rows in set (0.00 sec)
    
    150729 12:02:11     6 Connect   root@172.17.42.1 on
                        6 Query     select @@version_comment limit 1
    150729 12:02:12     6 Query     show databases
    150729 12:02:14     6 Quit
    

Dockerfileを使わない方法

mysqlイメージは、/etc/mysql/conf.dに追加の設定ファイルを入れておけば、起動時に読み込む仕組みのため、docker run 時にマウントすることでDockerfileを省略できる。

設定ファイルを保存するディレクトリを作り、先ほどのlog.cnfを移動する。

mkdir mysqlconf
mv log.cnf mysqlconf/

/etc/mysql/conf.dにマウントさせて起動させる。

docker run -e MYSQL_ROOT_PASSWORD=my-secret-pw -v `pwd`/mysqllog:/var/log/mysql -v `pwd`/mysqlconf:/etc/mysql/conf.d -p "3306:3306" -d mysql

方法2.クエリログの出力先を標準出力にする

kolypto/docker-mysqlを参考に

log.cnfを以下のようにする

[mysqld]
log_output=FILE
general_log=1
general_log_file=/dev/stdout

起動する

docker run -e MYSQL_ROOT_PASSWORD=my-secret-pw -v `pwd`/mysqlconf:/etc/mysql/conf.d -p "3306:3306"  mysql

しかし、起動中に

[ERROR] Could not open /dev/stdout for logging (error 2). Turning logging off for the whole duration of the MySQL server process. To turn it on again: fix the cause, shutdown the MySQL server and restart it.

となり、クエリログは出力されませんでした。

Dockerのバージョンは

$ docker --version
Docker version 1.7.1, build 786b29d

docker execで実行中のMySQLイメージに入り、コマンドラインで書き込みをテストしてみる。

$ docker ps
CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS              PORTS                    NAMES
bf5335a829bf        mysql               "/entrypoint.sh mysq   25 seconds ago      Up 24 seconds       0.0.0.0:3306->3306/tcp   boring_engelbart
$ docker exec -it bf5335a829bf bash
root@bf5335a829bf:/# apt-get update
root@bf5335a829bf:/# apt-get install -y sudo
root@bf5335a829bf:/# sudo -u mysql sh -c "echo test > /dev/stdout"
sh: 1: cannot create /dev/stdout: Permission denied

/dev/stdoutの権限を確認する。

root@bf5335a829bf:/# ls -l /dev/stdout
lrwxrwxrwx 1 root root 15 Jul 30 04:04 /dev/stdout -> /proc/self/fd/1
root@bf5335a829bf:/# ls -l /proc/self/fd/1
lrwx------ 1 root root 64 Jul 30 04:12 /proc/self/fd/1 -> /dev/pts/4
root@bf5335a829bf:/# ls -l /dev/pts/
total 0
crw-rw-rw- 1 root root 5, 2 Jul 30 04:04 ptmx

/dev/stderrも同様で、以下のissueが出ています。
Permission denied on /dev/stderr · Issue #6880 · docker/docker

ubuntuにphpenvからPHPをインストールした時のエラー

ubuntuをイメージベースに、phpenvを入れ、phpenvでPHP 5.6.9をインストールするDockerfileを作成。
docker buildしたのち、そのコンテナに入り、PHP 5.6.9をインストールした際、以下のようなエラーが発生した。

# phpenv install 5.6.9
[Info]: Loaded extension plugin
[Info]: Loaded apc Plugin.
[Info]: Loaded composer Plugin.
[Info]: Loaded pyrus Plugin.
[Info]: Loaded uprofiler Plugin.
[Info]: Loaded xdebug Plugin.
[Info]: Loaded xhprof Plugin.
[Info]: php.ini-production gets used as php.ini
[Info]: Building 5.6.9 into /root/.anyenv/envs/phpenv/versions/5.6.9
[Downloading]: http://php.net/distributions/php-5.6.9.tar.bz2
[Preparing]: /tmp/php-build/source/5.6.9
[Compiling]: /tmp/php-build/source/5.6.9
[Pyrus]: Downloading from http://pear2.php.net/pyrus.phar
[Pyrus]: Installing executable in /root/.anyenv/envs/phpenv/versions/5.6.9/bin/pyrus
[xdebug]: Installing version 2.3.2
[xdebug]: Compiling xdebug in /tmp/php-build/source/xdebug-2.3.2

-----------------
|  BUILD ERROR  |
-----------------

Here are the last 10 lines from the log:

-----------------------------------------
  write(sp[1], &s, sizeof(s));
       ^
/tmp/php-build/source/5.6.9/sapi/fpm/fpm/zlog.c: In function 'zlog_ex':
/tmp/php-build/source/5.6.9/sapi/fpm/fpm/zlog.c:191:8: warning: ignoring return value of 'write', declared with attribute warn_unused_result [-Wunused-result]
   write(zlog_fd > -1 ? zlog_fd : STDERR_FILENO, buf, len);
        ^
/tmp/php-build/source/5.6.9/sapi/fpm/fpm/zlog.c:195:8: warning: ignoring return value of 'write', declared with attribute warn_unused_result [-Wunused-result]
   write(STDERR_FILENO, buf, len);
        ^
PEAR package PHP_Archive not installed: generated phar will require PHP's phar extension be enabled.
-----------------------------------------

The full Log is available at '/tmp/php-build.5.6.9.20150605091024.log'.
[Warn]: Aborting build.

ビルドのためにインストールしたパッケージは以下の通り。

# apt-get install -y re2c bison libxml2-dev libcurl4-openssl-dev libjpeg8-dev libpng12-dev libmcrypt-dev libreadline-dev libtidy-dev libxslt1-dev

そこで、ubuntuにphp-buildでphp5.5を入れるためにシェルスクリプトと格闘した記録 | hirobanex.netより、autoconfをインストール

# apt-get install -y autoconf

もう一度インストールしなおす

# phpenv install 5.6.9
[Info]: Loaded extension plugin
[Info]: Loaded apc Plugin.
[Info]: Loaded composer Plugin.
[Info]: Loaded pyrus Plugin.
[Info]: Loaded uprofiler Plugin.
[Info]: Loaded xdebug Plugin.
[Info]: Loaded xhprof Plugin.
[Info]: php.ini-production gets used as php.ini
[Info]: Building 5.6.9 into /root/.anyenv/envs/phpenv/versions/5.6.9
[Skipping]: Already downloaded and extracted http://php.net/distributions/php-5.6.9.tar.bz2
[Preparing]: /tmp/php-build/source/5.6.9
[Compiling]: /tmp/php-build/source/5.6.9
[Pyrus]: Downloading from http://pear2.php.net/pyrus.phar
[Pyrus]: Installing executable in /root/.anyenv/envs/phpenv/versions/5.6.9/bin/pyrus
[xdebug]: Installing version 2.3.2
[xdebug]: Compiling xdebug in /tmp/php-build/source/xdebug-2.3.2
[xdebug]: Installing xdebug configuration in /root/.anyenv/envs/phpenv/versions/5.6.9/etc/conf.d/xdebug.ini
[xdebug]: Cleaning up.
[Info]: Enabling Opcache...
[Info]: Done
[Info]: The Log File is not empty, but the Build did not fail. Maybe just warnings got logged. You can review the log in /tmp/php-build.5.6.9.20150605093249.log
[Success]: Built 5.6.9 successfully.

作成したDockerfile