Dockerを使ってProductionを運用してみて数ヶ月。
以下の様な構成で(今のところ)安定して動いているので、備忘録としてまとめておきます。
DBのコンテナ化も考えましたが、データ永続化の面などでちょっと不安だったのでやめました。
なぜDockerを選択したか
まず背景として、大人の事情で1台のサーバ上に複数サービスを運用する必要がありました。
ApacheやTomcatを、別インスタンスで稼働させる手も考えましたが、
将来の移行コストやポート番号の管理問題もあり、Dockerを選択しました。
(というか、これならDockerでいけそう。やってみたい!というモチベーションがあったのが大きいです)
前提
- serviceA、serviceBをサーバ1台で運用
- serviceA、serviceBで同じデータベースを参照
- ドメイン部分は固定(ここではhoge.jpとする)。サブドメインを分けて、複数サービスを運用
- serviceA ->
https://serviceA.hoge.jp/....
- serviceB ->
https://serviceB.hoge.jp/....
- serviceA ->
- SSL通信
- serviceAをバージョンアップする際、serviceBは停止しないようにする(逆も同じ)
構成
nginx
-
役割
- リバースプロキシ
- serviceAに対するリクエストはlocalhost:49998へ、serviceBに対するリクエストはlocalhost:49999へ振り分けます
- 49998や49999は空いているポート番号の例です。適当に変えて下さい。
- SSLを解き、以降(サーバ内)の通信はhttpとします
- httpアクセスはhttpsにリダイレクトさせます
-
設定ファイルの内容↓
### serviceA ### server { listen 80; server_name serviceA.hoge.jp; location / { rewrite ^(.*)$ https://$host$1 permanent; } } server { listen 443; server_name serviceA.hoge.jp; ssl on; ssl_certificate {serviceAのSSLサーバ証明書パス}; ssl_certificate_key {秘密鍵パス}; location / { # 49998は後述のdockerコンテナのポート番号 proxy_pass http://localhost:49998; proxy_redirect http:// https://; # 接続元情報を維持 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-Proto https; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } ### serviceB ### server { listen 80; server_name serviceB.hoge.jp; location / { rewrite ^(.*)$ https://$host$1 permanent; } } server { listen 443; server_name serviceB.hoge.jp; ssl on; ssl_certificate {serviceBのSSLサーバ証明書パス}; ssl_certificate_key {秘密鍵パス}; location / { # 49999は後述のdockerコンテナのポート番号 proxy_pass http://localhost:49999; proxy_redirect http:// https://; # 接続元情報を維持 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-Proto https; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }
docker-compose
-
サービス毎にコンテナの管理や起動・停止など行いたかったので、"docker-compose"を使用しています
-
serviceAのdocker-compose.yml
# tomcat tomcat: build: tomcat container_name: serviceA-tomcat stdin_open: true tty: true volumes: - /etc/localtime:/etc/localtime:ro - /var/log/docker-container/serviceA/tomcat:/var/log/tomcat8:rw extra_hosts: - "docker-host:${DOCKER_HOSTIP}" # apache apache: build: apache container_name: serviceA-apache stdin_open: true tty: true volumes: - /etc/localtime:/etc/localtime:ro - /var/log/docker-container/serviceA/apache:/var/log/apache2:rw links: - tomcat ports: - "49998:80"
-
ポイント
-
extra_hostsの設定では、"docker-host"という名前でコンテナからホストを参照できるようにしています
-
${DOCKER_HOSTIP}は、以下シェルのワンライナーで取得してホストの環境変数に設定してあります
$ ip route show | grep docker0 | awk \"{print \\\$9}\"
-
-
49998でポートを公開しています。nginxのserviceA設定で指定するポート番号になります
-
volumesでは、タイムゾーンとログ出力ディレクトリをマウントしています
-
-
serviceBのdocker-compose.ymlも上記と同様に作成します。違いはservice名(A->B)とports部分(49998->49999)になります
dockerコンテナ(Ubuntu15.10)
-
serviceA-Apache
-
DockerFile
FROM ubuntu:15.10 MAINTAINER xxx <xxx@xxxx.xx> RUN rm -rf /var/lib/apt/lists/* && \ apt-get clean && \ apt-get update && \ apt-get install -y apache2 \ vim \ less \ net-tools \ nmap \ curl \ nkf \ inetutils-ping COPY serviceA-apache.conf /etc/apache2/sites-available/ RUN a2ensite serviceA-apache.conf && \ a2enmod proxy proxy_http rewrite ## set locale RUN locale-gen ja_JP.UTF-8 ENV LANG ja_JP.UTF-8 ENV LANGUAGE ja_JP:en ENV LC_ALL ja_JP.UTF-8 EXPOSE 80 ENV APACHE_RUN_USER www-data ENV APACHE_RUN_GROUP www-data ENV APACHE_PID_FILE /var/run/apache2.pid ENV APACHE_RUN_DIR /var/run/apache2 ENV APACHE_LOCK_DIR /var/lock/apache2 ENV APACHE_LOG_DIR /var/log/apache2 ## tail -f ... はコンテナが途中終了しないためのおまじない命令 ENTRYPOINT service apache2 start && tail -f /dev/null
-
serviceA-apache.conf(docker-composeでlinkさせているtomcatコンテナへリクエストを投げます)
. . <Location /> ProxyPass http://serviceA-tomcat:8080/ </Location> . .
-
-
serviceA-Tomcat
-
DockerFile
FROM ubuntu:15.10 MAINTAINER xxx <xxx@xxxx.xx> RUN rm -rf /var/lib/apt/lists/* && \ apt-get clean && \ apt-get update && \ apt-get -y upgrade && \ apt-get install -y vim \ less \ net-tools \ nmap \ curl \ nkf \ inetutils-ping ## java8,tomcat8 ENV JAVA_HOME /usr/lib/jvm/java-8-oracle RUN echo oracle-java8-installer shared/accepted-oracle-license-v1-1 select true | debconf-set-selections && \ apt-get install -y software-properties-common && \ add-apt-repository -y ppa:webupd8team/java && \ apt-get update && \ apt-get install -y oracle-java8-installer \ tomcat8 ENV PATH $PATH:$JAVA_HOME/bin ## set locale RUN locale-gen ja_JP.UTF-8 ENV LANG ja_JP.UTF-8 ENV LANGUAGE ja_JP:en ENV LC_ALL ja_JP.UTF-8 ## tomcat ENV CATALINA_HOME /usr/share/tomcat8 ENV CATALINA_BASE /var/lib/tomcat8 RUN mkdir -p $CATALINA_BASE/bin && \ chown -R tomcat8:tomcat8 $CATALINA_BASE/bin RUN mkdir -p $CATALINA_HOME/common/classes && \ mkdir -p $CATALINA_HOME/server/classes && \ mkdir -p $CATALINA_HOME/shared/classes && \ mkdir -p $CATALINA_HOME/temp && \ rm -rf $CATALINA_BASE/common && \ rm -rf $CATALINA_BASE/server && \ rm -rf $CATALINA_BASE/shared && \ rm -rf $CATALINA_BASE/temp && \ rm -rf $CATALINA_BASE/webapps/ROOT && \ ln -s $CATALINA_HOME/common $CATALINA_BASE/common && \ ln -s $CATALINA_HOME/server $CATALINA_BASE/server && \ ln -s $CATALINA_HOME/shared $CATALINA_BASE/shared && \ ln -s $CATALINA_HOME/temp $CATALINA_BASE/temp EXPOSE 8080 ## tail -f ... はコンテナが途中終了しないためのおまじない命令 ENTRYPOINT $CATALINA_HOME/bin/startup.sh && tail -f /dev/null
-
-
serviceB-Apache
- 上記と同様に作成
-
serviceB-Tomcat
- 上記と同様に作成
postgreSQL
-
コンテナからpostgreSQLへの接続方法は以下になります(JDBC接続、ポート5432の場合)
jdbc:postgresql://docker-host:5432/
-
"docker-host"はdocker-composeのextra_hostsで設定しています
サービス起動
$ cd {docker-compose.ymlがあるディレクトリ}
$ docker-compose up -d
DevOps
- 本サービスでは以下ツールを使用しています。この辺りの詳細は後日また書こうと思ってます。
- Itamae(プロビジョニング)
- Jenkins(CI)
- Fablic(デプロイ)
- idobata(チャット)
- Flyway(DBマイグレーション)
まとめ
パフォーマンスや障害発生時の切り分けなど、不安だった点はいくつかありましたが、
現状それに関しても問題なく運用出来ています。
運用・開発者がDockerに関する知識を最低限持つ必要がありますが、結果Dockerで構築してみて良かったと思ってます。
サービスによっては、インフラ構築の際の有力な選択肢としてDockerを加えてもいいと感じています。
追記(2017/01/25)
- 本番運用していて少し困ったこと
- yumやaptのパッケージアップデート時にDockerに更新がかかった場合、Dockerコンテナが停止してしまいます
- パッケージアップデートでDockerを除外してしまうと、カーネルが更新された時とか不具合起こしそうだからやめた方がよさそう(Dockerはホストとコンテナでカーネルが共有されている)