- はじめに
- Docker?
- 構築
- おわりに
はじめに
Dockerはコンテナ仮想化技術のデファクトスタンダードとなっている。
そんなDockerをやるやる詐欺を繰り返し未だ初歩的な知識も欠如している筆者が、夏休みの宿題も兼ねてDockerを使えるようになろうというのが目的。
タイトルがやたら盛りだくさんだが、同じくやるやる詐欺を繰り返していた者達なので個々の詳しいことはこれから学ぶ予定。
とりあえず、Dockerを用いて環境を構築することが今回のゴール。
Docker?
Dockerを勉強とすると言っても何から始めればいいのか?
筆者も苦労したが基本的な概念とイメージを掴むにはKatacodaというeラーニングサイトで学ぶのが良いと感じた。
KatacodaではWebブラウザ上でDockerを直接操作しながら学習することができる。
実際にコマンドを叩いてどういう出力がでて、結果どうなるのかという流れを短時間で準備なく試せるのは非常に快適。
基本的にはKatadcodaのDockerをコースを受講していればよいが、すでに非推奨となったlinkオプションを用いたコースなどもそのまま存在するので公式ドキュメント等で情報を補足しながら利用するのが良い。
幸いドキュメント日本語化プロジェクトが存在しており日本語で読むことができるので英語が辛くても安心。
そして、概念を掴んだ上でここを理解しておけば大きな誤解なく進められるんじゃないか?と思ったのが下記の項目
- イメージとコンテナの違いを理解すること
- これがはっきりしていないと後に出てくるDockerfileとdocker-compose.ymlを用いる際わけがわからなくなってしまった。
- イメージに対しての操作なのか、コンテナに対しての操作なのかを常に意識すれば理解しやすくなった。
- Dockerはプロセスをフォアグラウンドで動作させないとコンテナが停止してしまう。
- 当たり前のようにNginx起動のコマンドに"daemon off"がついているけどそういう理由
- Nginx以外でも同様の対策が必要。
- Data Volumeについて大まかに理解をしておくことが大事。
- 下記がかなり参考になった
- 早い段階でコンテナのデータは保持されず、永続化にはData Volumeを使う必要があることがわかる。
- 基本的には理解しやすいbindを用いればあまり悩まずに済みそうという感想。
- 頻繁に更新するファイルなどはbindが良さそうだけどそれ以外はvolumeを用いたほうが良さそう(要出典)なので使うとよいけど、無名のvolumeを作成しないようにすると良い。
- docker-composeに頼る。
- ymlを書くことで設定が可視化でき、Data Volumeなどもいい感じにしてくれるので概念さえつかめたらあとはdocker-composeでやったほうが圧倒的に楽だった。
- DockerHubに書いてあるOSのディストリビューションを理解しておく。
- Dockerイメージの容量を小さくする為に用いられることが多い「Alpine Linux」
- alpineと書いてあったらOSはこれ。パッケージ管理は「apk」シェルは「ash」なので注意
- Debianと書いてなくても「buster」って書いてあればDebian
- Debian 10のコードネーム。知らなかったので何者だ?と初見で思った。
- Dockerイメージの容量を小さくする為に用いられることが多い「Alpine Linux」
とりあえず以上。これがわかったのでだいたい大丈夫なんじゃない?と一安心してる感じ。
構築
ようやく本題。これから構築の話を書いていくけど基本的な思想としてはなるべく自分でDockerfileは書きたくなくてDockerHubのOfficialイメージを使っていい感じに構築したいという感じ。
構築したいもの
docker-composeを用いてRailsアプリケーションがMySQLに接続しつつ、Nginxをリバースプロキシとして利用する。
アプリケーションサーバはpuma。
NginxのログファイルをFluentdでElasticsearchに送りKibanaで見ることができるもの。
各バージョン
- Docker 19.03.1
- docker-compose 1.24.1
- MySQL 8.0.17
- Ruby 2.6.3
- Rails 6.0.0
- Nginx 1.17.2
- Elasticsearch 7.3.0
- Kibana 7.3.0
- Fluentd 1.6.3
ディストリビューションは選択肢があればDebianを選んでる。よくわからない事が起きたときコンテナに入って調べるときAlpineだときつかったので。
$ docker-compose run name bash
上記でnameのコンテナにbashで入れるから困ったらこれしてる。
MySQL
基本的にはDockerHubに記載されているdocker-composeを利用するが2箇所変更した。
参考
db: image: mysql command: --default-authentication-plugin=mysql_native_password restart: always environment: MYSQL_ROOT_PASSWORD: examplehttps://hub.docker.com/_/mysql
docker-compose.yml
version: '3.7' services: db: image: mysql:8.0.17 command: --default-authentication-plugin=mysql_native_password restart: always volumes: - db-data:/var/lib/mysql environment: - MYSQL_ALLOW_EMPTY_PASSWORD=yes ports: - 3306:3306 volumes: db-data:
変更点
- rootのパスワード
- 開発用ということもありrootのパスワードはなしにしたかったためMYSQL_ALLOW_EMPTY_PASSWORDを設定している。
- Environment Variablesの説明はDockerHubにあるので用途に応じて設定してほしい。
- ポートフォワーディング
- 加えてホストからアクセスするためにports設定を足した。ホストにMySQLは起動していないので3306同士。
- ホストにMySQLが起動している場合はホスト側のポートを変えれば両立可能(例:6666:3306)
- データボリューム
- トップレベルにdb-dataという名前付きのボリュームを定義している。
- オプションが色々設定可能だが、オプションなしだとボリュームが存在しない場合docker-composeが名前付きのボリュームを作成してくれる。
- これでMySQLのデータの永続化を実現している。
注意点
- https://qiita.com/Lio/items/3127685e26081993b51e
- portsの設定をしているのでホストの3306ポートからアクセスしようとlocalhostでアクセスしようとするとホストで起動してるMySQLと接続しようとしてつながらない罠。
- hオプションの値を127.0.0.1にすると接続できる。
- MySQL8でデフォルトの認証形式になったcaching_sha2_password
- docker-compose.ymlのcommandを見てもらえばわかるが、起動オプションにdefault-authentication-plugin=mysql_native_passwordが指定されている。
- これはMySQL8からデフォルトの認証プラグインが変わったためcaching_sha2_passwordにクライアントが対応していないとエラーになるため。
- caching_sha2_passwordに対応していく必要はあるが、今回の趣旨ではないので従来のmysql_native_passwordにしておいた。
Rails
Docker本家にドキュメントがありそれを参考にRails6 betaを導入する記事を参考させてもらった。
Rails6になり必要になったものなどをインストールする必要があったためDockerfileでイメージをビルドすることに。
参考
手順に則りDockerfile,entrypoint.sh,Gemfile,Gemfile.lockを作成。Gemfile.lockは空ファイルで良い。
Dockerfile
FROM ruby:2.6.3-buster # nodejs # https://github.com/nodesource/distributions/blob/master/README.md RUN curl -SL https://deb.nodesource.com/setup_12.x | bash # yarn # https://yarnpkg.com/lang/en/docs/install/#debian-stable RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list RUN apt-get update -qq && apt-get install -y nodejs yarn RUN mkdir /myapp WORKDIR /myapp COPY Gemfile /myapp/Gemfile COPY Gemfile.lock /myapp/Gemfile.lock RUN bundle install COPY . /myapp # Add a script to be executed every time the container starts. COPY entrypoint.sh /usr/bin/ RUN chmod +x /usr/bin/entrypoint.sh ENTRYPOINT ["entrypoint.sh"] EXPOSE 3000 # Start the main process. CMD ["rails", "server", "-b", "0.0.0.0"]
Node.jsとyarnはDockerfile内コメントにあるURLから最新版ぽいバージョンを選択している。
entrypoint.sh
#!/bin/bash set -e # Remove a potentially pre-existing server.pid for Rails. rm -f /myapp/tmp/pids/server.pid # Then exec the container's main process (what's set as CMD in the Dockerfile). exec "$@"https://docs.docker.com/compose/rails/
そのままの為引用。pidファイルが残っていて起動できないケースを防ぐ為。
Gemfile
source 'https://rubygems.org' gem 'rails', '6.0.0'
docker-compose.yml
追記部分以外は省略
services: web: build: . command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" volumes: - .:/myapp ports: - 3000:3000 depends_on: - db
rails newをする。
$ docker-compose run web rails new . --force --no-deps --database=mysql
WORKDIRを/myappに設定しているためこの指定で/myapp配下にrails newされる。
そして/myappはホストのカレントディレクトリをbindしてるためホストのカレントディレクトリにもrails newしたファイルが存在する。
Macで作業しているためファイルの所有権は問題ないのでスキップ。
Gemfileが更新された(rails newによって)のでbuildする。Gemfileが更新されたらbuildする必要があるようだ。
$ docker-compose build
database.ymlのhostをdocker-compose.ymlで指定しているサービス名に変更する。
今回の場合はdbなので、host: dbとすればよい。
database.yml
default: &default adapter: mysql2 encoding: utf8mb4 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root password: host: db
ようやくupできる。
$ docker-compose up
portsで設定しているのでhttp://localhost:3000でアクセスできるはず。
アクセスするとDBがないと怒られるのでdbを作成する。
$ docker-compose run web rake db:create
これでYay! You’re on Rails!に到達できる。
Nginx
次にpumaのリバースプロキシにNginxを使うための設定を行う。
まず、pumaの設定。unixソケット通信に変更してNginxと連携できるようにする。
config/puma.rb
--- a/config/puma.rb +++ b/config/puma.rb @@ -10,7 +10,10 @@ threads min_threads_count, max_threads_count # Specifies the `port` that Puma will listen on to receive requests; default is 3000. # -port ENV.fetch("PORT") { 3000 } +#port ENV.fetch("PORT") { 3000 } + +# unix socket +bind "unix://#{Rails.root}/tmp/sockets/puma.sock" # Specifies the `environment` that Puma will run in. #
--- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,13 +13,23 @@ services: - 3306:3306 web: build: . - command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" + command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s"
pやbのオプションがついているとunixソケットを使ってくれないのでこちらも修正。
nginx.confも作る。適切なディレクトリ構成とかは一旦置いといてカレントに作成。
upstream myapp { server unix:///myapp/tmp/sockets/puma.sock; } server { listen 80; server_name localhost; access_log /var/log/nginx/myapp_access.log; error_log /var/log/nginx/myapp_error.log; root /myapp/public; try_files $uri/index.html $uri @myapp; location @myapp { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_pass http://myapp; } }
docker-compose.yml
追記部分以外は省略
services: proxy: image: nginx:latest command: "nginx -g 'daemon off;'" volumes: - .:/myapp - ./nginx.conf:/etc/nginx/conf.d/default.conf ports: - 80:80 depends_on: - web
unixソケットを利用するのでsockファイルを参照できるようにカレントディレクトリをbind。
nginxデフォルトのconfファイルはひとまず必要ないので上書きしてしまう。
これでhttp://localhostにアクセスするとYay! You’re on Rails!に到達できる。
注意点
- Dockerコンテナ上で、ログファイルにしかログを書かないソフトウェアに標準出力・標準エラー出力を使わせるようにする - CLOVER🍀
- 関連: View logs for a container or service | Docker Documentation
- docker container logsやdocker-compose upをフォアグラウンドで実行していると見ることのできるログに、各コンテナで起きたログを表示するには標準出力、標準エラー出力に内容を書く必要がある。
- Nginxはアクセスログをログファイルに書きつつ標準出力に書き出す方法がないので、アクセスログを標準出力にシンボリックリンクすることで実現している。
- そのため/var/log/nginx/access.logにいくらログを書いてもコンテナ内にログは残らなくなっている。
# ls -l /var/log/nginx/ total 0 lrwxrwxrwx 1 root root 11 Aug 15 21:22 access.log -> /dev/stdout lrwxrwxrwx 1 root root 11 Aug 15 21:22 error.log -> /dev/stderr
今回はログファイルに書き込み、Fluentdで扱いたかったのでファイル名を変えてログファイルを残すことにした。(myapp_access.log)
Elasticsearch+Kibana
docker-compose.yml
elasticsearch: image: elasticsearch:7.3.0 environment: - discovery.type=single-node ports: - 9200:9200 volumes: - elastic-data:/usr/share/elasticsearch/data kibana: image: kibana:7.3.0 ports: - 5601:5601 depends_on: - elasticsearch volumes: elastic-data:
DockerHubの公式イメージからコンテナを立ち上げるだけ。
データ永続化にはトップレベルにelastic-dataというボリュームを作成して実現。
http://localhost:5601/にアクセスするとKibanaにアクセスできる。データはまだ無い。
Fluentd
ちょっとスマートなやり方ができなかったのでもう少しなんとかしたい課題あり。
- gemインストールするためだけにDockerfileを書きたくない。
- 今回の要件はただKibanaでみたいだけなので、ログファイルを残す必要が特に無い。そのためアクセスログを直接Fluentdに渡したかった。
- Dockerには直接関係ないので今回はやらない。
参考
Fluentdの公式イメージはDockerHubにあるが、Elasticsearchを使用するためにはgemをインストールする必要があるのでFluentd用にDockerfileを作る。
fluentd/Dockerfile
FROM fluent/fluentd:edge-debian USER root RUN gem install fluent-plugin-elasticsearch
設定ファイルも同ディレクトリに作る。
fluentd/fluent.conf
<source> @type tail format ltsv path /var/log/nginx/myapp_access.log tag nginx pos_file /var/log/nginx/myapp_access.log.pos </source> <match nginx> @type elasticsearch host elasticsearch buffer_type memory port 9200 index_name fluentd type_name nginx logstash_format true logstash_prefix nginx.access </match>
細かい意味はわかってない...
docker-compose.yml
--- a/docker-compose.yml +++ b/docker-compose.yml @@ -26,6 +26,7 @@ services: volumes: - .:/myapp - ./nginx.conf:/etc/nginx/conf.d/default.conf + - http-log:/var/log/nginx ports: - 80:80 depends_on: @@ -44,6 +45,16 @@ services: - 5601:5601 depends_on: - elasticsearch + fluentd: + build: ./fluentd + volumes: + - http-log:/var/log/nginx + - ./fluentd/fluent.conf:/fluentd/etc/fluent.conf + ports: + - 24224:24224 + depends_on: + - elasticsearch volumes: db-data: elastic-data: + http-log:
nginxのログをfluentdで扱いたいのでログディレクトリをデータボリュームに。
KibanaでNginxのログファイルを見る
- http://localhost:5601でKibanaにアクセスする
- Management>Index patterns>Create index patternを選ぶ
- index patternを作成する
- 時系列で見たいので@timestampを指定する(?)
- Discoverに行くとそれっぽいグラフが表示される。
最終的なdocker-compose.yml
version: '3.7' services: db: image: mysql:8.0.17 command: --default-authentication-plugin=mysql_native_password restart: always volumes: - db-data:/var/lib/mysql environment: - MYSQL_ALLOW_EMPTY_PASSWORD=yes ports: - 3306:3306 web: build: . command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s" volumes: - .:/myapp ports: - 3000:3000 depends_on: - db proxy: image: nginx:latest command: "nginx -g 'daemon off;'" volumes: - .:/myapp - ./nginx.conf:/etc/nginx/conf.d/default.conf - http-log:/var/log/nginx ports: - 80:80 depends_on: - web elasticsearch: image: elasticsearch:7.3.0 environment: - discovery.type=single-node ports: - 9200:9200 volumes: - elastic-data:/usr/share/elasticsearch/data kibana: image: kibana:7.3.0 ports: - 5601:5601 depends_on: - elasticsearch fluentd: build: ./fluentd volumes: - http-log:/var/log/nginx - ./fluentd/fluent.conf:/fluentd/etc/fluent.conf ports: - 24224:24224 depends_on: - elasticsearch volumes: db-data: elastic-data: http-log:
おわりに
開発環境でdocker-composeを使った開発はできるようになったんじゃないかという感じだが、まだまだ解決しなければいけない課題はたくさんありそう。
- RailsアプリーケーションログもKibanaでみたい。
- 適切なディレクトリ構成やどのようにGit管理すべきか。
- docker-composeを作成者以外に利用して貰う場合にはどうするのがいいか
- Dockerfileでやるかdocker-compose.ymlでやるか
などなど。だが、ひとまず一定の成果が得られてよかった。
FluentdやElasticsearchについてほぼ運用的な知識がないので、環境も作れるようになったことだし色々と調べないとね。