これはなに?
こんにちわ!ナギちゃんだよー☆
きょーは前回セットアップしたVPSをつかってdocker-composeでチンするだけのお手軽タピオカ時短コンテナをたてるよー!
はい。
長いので
- VPSセットアップ編
- コンテナ構築編(いまここ)
- Nuxt+Wordpress編
に分割されています。
これはなんですか?
Nuxt+Wordpress+開発用コンテナ群をつくります。開発用コンテナはVPN接続時しか見れないすぐれもの!
なにがめんどくさいですか?
docker勉強用にはじめたものの地雷を多数ふんでしまい満身創痍になりました。勉強用ならもっと簡単なところからはじめたほうがよかった・・・
Let's encrypt
みんなやってるから簡単だろー?とか思ったら地味にめんどくさかったです。
たしかにぐぐるといろいろな手段があるようで
◆nginxコンテナ+https-portalコンテナ
・https-portalというLet's Encryptの取得+更新をすべて自動でやってくれるコンテナがある
・https-portalのnginx > webapp用のnginx > webappというめんどい経路になる
・(いじった範囲では)https-portalのnginx用confをこちらで操作するとうまくうごきませんでした
・(いじった範囲では)用意されている変数でリダイレクト先指定するとURLにいらないものがつくのでリネームがめんどそう
◆nginx-proxyコンテナ+docker-letsencrypt-nginx-proxy-companionコンテナ
・nginx-proxyというコンテナ群を監視し自動でproxyしてくれるコンテナがある
・nginx~companionというLet's Encryptの取得+更新をすべて自動でやってくれるコンテナがある
・開発用コンテナもまざるので自動でproxyとかやってほしくない(設定をしらべるのもめんどう)
◆nginxコンテナ+certbotコンテナ
・certbotがうごくだけの公式コンテナがある(自動更新などはない)
・certbot動作時は外部Port80で通信できる必要がある + 同じ外部ポート指定で動作するコンテナは作れない
・(初回)ssl証明書がない > nginxが起動しない > nginxコンテナ終了
・(初回)nginxが起動しない> certbotが認証できない(上とあわせてデッドロック)
・ただしcertbotはhttp://xxxx/.well-known/acme-challenge/
にのみアクセスできれば認証できる
◆1つのコンテナにnginx+certbotインストール
・上とほぼ同様で、さらにnginxコンテナに余分なものがついてしまう
というかんじになります。
多くの人が悩んでいるようで、自作イメージを配布されている方もいるのですが、何が入っているかわからないイメージは使用しないは鉄則そうなのと、初回さえどうにかなれば綺麗にすみそうということで、今回は3番目でいくことにします。
ネットワーク設定
portsを指定したコンテナは、グローバルで公開され、firewalldによるブロックは効きません
たとえば「firewalldで80,443のみ許容」していても、「mariadbコンテナのportsを3306」のように指定すると、なにごともなくyourdomain:3306
で外部からアクセスできてしまいます。これはdockerがiptablesを直接操作するためのようです。
(とはいえiptablesをみると、firewalldで設定したポートスキャンのあたりは残っているようなので、firewalldで設定する意味がまったくないわけではないようです)
docker-composeであげたコンテナ群はymlに書いておけば自動でbridgeされるので、とりあえずコンテナ間でさえ通信できれば良いコンテナにはportsを一切指定しないようにします。ports:80
のように内部だけ設定していても、30000以上の任意のポートに割り振られるので外部に穴があいていることに変わりはないはずです。
また、特定のIPアドレスのみ許容したいコンテナは、左辺にIPアドレスまで指定できます(ex:192.168.1.252:3306:3306
)
コンテナ間の接続もなにやってるかわからなくてかなり怖いので、実運用するならもっと勉強しなければなりませんね・・・。
###VMと同等なCentOSコンテナ
開発用コンテナとして、Nuxtが動作し、sshで直接ログインでき、sudoもsystemctlも使えるなど、ほぼVMのように使えるものを目指しました。
コンテナのアンチパターンであがってそうな感じですが、VSCode Remote Developmentが正式にリリースされたので、今後そこそこ需要あるのでは?とも思います。
とはいえsystemctlやssh用rsaファイルの権限あたりで慣れていないのでかなり疲れました・・・。けっきょく**/sbin/initで起動しつつbashコマンドもやる方法が見つけられなかったので** nuxtのセットアップはshにまとめておいてあとでdocker execする・・・という風にしています。
じゅんび?
ディレクトリ作成
作業するためのディレクトリと、コンテナ内部のデータを保存するディレクトリを作成します
コンテナ作成作業ディレクトリ
コンテナをつくるためのもろもろ材料を置く作業用ディレクトリを作成します。
前回作成した環境であれば、ユーザにはsudo+docker権限があるので、ログインしたユーザでそのまま作業ができます。好きなところに以下のようにディレクトリを作成し、これから記述していく各Dockerfileやconfを設置してください。
/container
| #dockerまとめて設定ファイル
├── docker-compose.yml
|
| #プロキシ用nginxコンテナ用ディレクトリ
├── /nginx
| └── nginx.conf
|
| #certbot動かすコンテナ用ディレクトリ
├── /certbot
|
| #Nuxtで作成されたアプリの公開用コンテナ用ディレクトリ
├── /webapp
| └── Dockerfile
|
|
| #wordpressコンテナ用ディレクトリ
├── /wordpress
|
| #wordpress用mariadbコンテナ用ディレクトリ
├── /wordpress_db
| └── my.conf
|
| #Nuxtで開発するためのCentOSコンテナ用ディレクトリ
└── /webapp_dev
├── Dockerfile
├── sshd.conf
├── id_rsa.pub
└── startup_nuxt.sh
[user01@hostname ~]# mkdir container
[user01@hostname ~]# mkdir container/nginx
[user01@hostname ~]# mkdir container/certbot
[user01@hostname ~]# mkdir container/webapp
[user01@hostname ~]# mkdir container/webapp_dev
[user01@hostname ~]# mkdir container/wordpress
[user01@hostname ~]# mkdir container/wordpress_db
※なんとなくぜんぶ作っていますが、いらない(なにもファイルいれない)ディレクトリもあるのでお好みで
コンテナ内部からホスト側ディレクトリを参照するもの
コンテナてきには、外部データは最小限にしたいのですが、今回は
- SSL証明書ファイル一式: 先述の理由もあり、コンテナ起動時に存在していないと初回起動できないため
- Wordpressのファイル一式: wordpressの設定は永続化してほしいため
- データベースのイメージ: wordpress記事を保存するため
としました。ほかは
- 公開用/開発用ファイルはコンテナ起動時にgithubからcloneし、本体には残さないようにします
- それぞれのディレクトリは、慣習的に楽かなぁと思い、ホストにインストールしたときと同じような位置にしています
- が、普段はインストール時につくられるものが存在しないので、ホスト側にディレクトリを作成する必要があります
[user01@hostname ~]# sudo mkdir /etc/letsencrypt
[user01@hostname ~]# sudo mkdir /var/www
[user01@hostname ~]# sudo mkdir /var/lib/mysql
[user01@hostname ~]# sudo mkdir /var/www
[user01@hostname ~]# sudo mkdir /var/www/html
[user01@hostname ~]# sudo mkdir /var/www/html/wordpress
初回certbot
先述したように、SSL用ファイルがないとnginxを起動できません。この初回起動のために、certbotコンテナを準備して、SSL認証用ファイルを生成します。成功すれば、IMPORTANT NOTES:Congratulations!
の表示とともに、etc/letsencrypt
ディレクトリにいろいろなファイルができているはずです。
なお、このcertbotイメージは今後も使っていくので削除する必要はありません。
[user01@hostname ~]# docker image pull certbot/certbot
[user01@hostname ~]# docker run --rm -p 443:443 -p 80:80 --name certbot-certonly -v "/etc/letsencrypt:/etc/letsencrypt" certbot/certbot certonly -n -m "${メールアドレス}" -d ${ドメイン} --standalone --agree-tos
githubリポジトリ作成
webappコンテナがファイルをDLするためにつくっておきます。この段階では空でもかまいません。
プライベートリポジトリの場合は、cloneするのに認証が必要となるので、githubのSettings > Developer settings > Personal access tokens
からアクセストークンを作成してください。https://${ユーザ名}:1234567890awsedrftgyhujikolp@github.com/${ユーザ名}/${リポジトリ名}
みたいなやつが生成できれば、このURLからダウンロードすることができます。
docker-compose
ながくなりましたが、やっとメインとなるdocker-composeをかいてみます。YAMLとかさわったことがなくて病むる。
version: '3'
services:
nginx:
image: nginx:1.17.0-alpine
container_name: nginx
restart: always
ports:
- "${グローバルIP}:80:80"
- "${グローバルIP}:443:443"
depends_on:
- wordpress
volumes:
- /etc/letsencrypt:/etc/letsencrypt/
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
certbot:
image: certbot/certbot
container_name: certbot
volumes:
- /etc/letsencrypt:/etc/letsencrypt
- certs:/var/log/letsencrypt
- certs:/var/lib/letsencrypt
webapp:
container_name: webapp
restart: always
image: webapp
build:
context: ./webapp
dockerfile: Dockerfile
tty: true
depends_on:
- wordpress
command: >
sh -c 'git clone ${githubのトークン+URL} &&
cd ${プロジェクト名(cloneしたときに作成されるディレクトリ名)} &&
yarn install &&
npm run build &&
npm start'
wordpress:
image: wordpress:5.2.1-php7.2-apache
container_name: wordpress
restart: always
ports:
- "${VPNローカルIP}:3180:80"
depends_on:
- wordpress_db
environment:
WORDPRESS_DB_HOST: wordpress_db:3306
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: ${データベースのパスワード}
WORDPRESS_DB_NAME: wordpress
volumes:
- /var/www/html/wordpress:/var/www/html/
wordpress_db:
image: mariadb:10.4.5-bionic
container_name: wordpress_db
restart: always
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: ${データベースのパスワード}
volumes:
- /var/lib/mysql:/var/lib/mysql
- ./mysql/my.conf:/etc/mysql/my.conf
webapp_dev:
container_name: webapp_dev
image: webapp_dev
build:
context: ./webapp_dev
dockerfile: Dockerfile
args:
ROOT_PSWD: "${開発用コンテナのROOTパスワード}"
USER_NAME: "${開発用コンテナのユーザ名}"
USER_PSWD: "${開発用コンテナのパスワード}"
privileged: true
restart: always
ports:
- "${VPNローカルIP}:${開発用コンテナSSHポート}:${開発用コンテナSSHポート}"
- "${VPNローカルIP}:${開発用コンテナWEBポート}:3000"
depends_on:
- wordpress
command: ["/sbin/init"]
volumes:
certs:
nginxコンテナ
インターネット公開用のneginxコンテナを作成します。
- ドメイン:Port80 → Port443にリダイレクト(デフォルト)
- ドメイン:Port443 → 公開用webappコンテナにリダイレクト
シンプルな構成なのでnginxイメージそのまま使えます。つまりDockerfileは不要です。
###nginx.conf
httpでhttpsにリダイレクト、httpsでwebappコンテナのExpressにリダイレクトします。
(参考にさせて頂きました: NginxでのSSL設定の細かい意味)
user nginx nginx;
worker_processes 3;
worker_cpu_affinity 001 010 100;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
multi_accept off;
worker_connections 1024;
use epoll;
}
http {
include mime.types;
default_type application/octet-stream;
error_log /var/log/nginx/error.log;
access_log /var/log/nginx/access.log;
server_tokens off;
server_name_in_redirect off;
port_in_redirect off;
sendfile on;
tcp_nopush on;
keepalive_timeout 5;
gzip_static on;
gzip on;
gzip_http_version 1.0;
gzip_vary on;
gzip_comp_level 2;
gzip_types text/plain text/xml text/css text/javascript
application/xhtml+xml application/xml
application/rss+xml application/atom_xml
application/javascript application/x-javascript
application/x-httpd-php;
gzip_disable "MSIE [1-6]\.";
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=one:32m max_size=512m inactive=1d;
proxy_temp_path /var/tmp/nginx;
proxy_cache_key "$scheme://$host$request_uri";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Accept-Encoding "";
proxy_connect_timeout 5;
proxy_send_timeout 10;
proxy_read_timeout 120;
proxy_hide_header X-Pingback;
proxy_hide_header X-Powered-By;
proxy_hide_header Etag;
proxy_hide_header Vary;
proxy_cache_use_stale timeout invalid_header http_500 http_502 http_503 http_504;
proxy_cache_lock on;
proxy_cache_lock_timeout 5s;
server {
listen 80 default_server;
server_name ${ドメイン名};
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name ${ドメイン名};
charset UTF-8;
ssl on;
ssl_certificate /etc/letsencrypt/live/ge-no.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ge-no.com/privkey.pem;
location / {
proxy_pass https://wordpress:80/;
proxy_redirect default;
}
}
}
#certbotコンテナ
さきほどDLしたものそのままでOKです。Dockerfileは不要です。
#webappコンテナ
インターネットに公開するNuxt.jsアプリのコンテナです。
ビルド時にnuxt環境をととのえておきます。
FROM node:12.4.0-alpine
WORKDIR /webapp
ENV LANG C.UTF-8
ENV TZ Asia/Tokyo
RUN apk update && \
apk add git && \
npm install -g npm && \
npm install -g yarn && \
npm install -g nuxt
#wordpressコンテナ
wordpress+wordpressAPIが動作するだけのコンテナです。Dockerfileは不要です。
wordpressイメージは各種バージョンのほかにも、apacheいり、php-fpm版などもあります。今回はバックエンドとして使うためphp-fpmがほしいところではありますが、編集画面にアクセスするのにnginx側で設定するのも面倒なので、素直にapache版を使用しました。CLIを使用してセットアップできればよりかっこよさそうですが、ディレクトリまるまる外部化したおかげもあり、Wordpressのセットアップはコンテナが再起動しても保持されるので、初回のみブラウザからアクセスして設定するところは妥協します。
#mariadbコンテナ
Oracleはイヤなので wordpress用mariadbです。Dockerfileは不要です。
my.confはUTF-8の設定と、イメージを適時分割するように指定したのみです。
my.conf
[mysqld]
pid-file = /var/run/mysqld/mysqld.pid
socket = /var/run/mysqld/mysqld.sock
datadir = /var/lib/mysql
secure-file-priv= NULL
symbolic-links=0
character-set-server=utf8
default_authentication_plugin = mysql_native_password
innodb_data_file_path=ibdata1:1G
innodb_file_per_table
!includedir /etc/mysql/conf.d/
#webapp_devコンテナ
webappコンテナ用のアプリを開発する場所です。
FROM centos:centos7.6.1810
ARG ROOT_PSWD
ARG USER_NAME
ARG USER_PSWD
RUN yum -y update && yum clean all && \
yum -y install epel-release openssh-server sudo git
RUN echo "root:$ROOT_PSWD" |chpasswd
RUN useradd $USER_NAME
RUN echo "$USER_NAME:$USER_PSWD" |chpasswd
RUN echo "$USER_NAME ALL=(ALL) ALL" >> /etc/sudoers
RUN curl -sL https://rpm.nodesource.com/setup_11.x | bash -
RUN yum -y install nodejs yarn && \
npm install -g yarn
RUN mkdir /home/$USER_NAME/.ssh && \
chmod 700 /home/$USER_NAME/.ssh && \
chown $USER_NAME /home/$USER_NAME/.ssh/ && \
/usr/bin/ssh-keygen -t dsa -f /etc/ssh/ssh_host_dsa_key -C '' -N ''
COPY sshd.conf /etc/ssh/sshd_config
COPY id_rsa.pub /home/$USER_NAME/.ssh/authorized_keys
COPY startup_nuxt.sh /home/$USER_NAME/startup_nuxt.sh
RUN chown $USER_NAME /home/$USER_NAME/.ssh/authorized_keys && \
chown $USER_NAME /home/$USER_NAME/startup_nuxt.sh
WORKDIR /home/$USER_NAME
Protocol 2
Port ${開発用コンテナ用SSHポート}
ListenAddress 0.0.0.0
SyslogFacility AUTHPRIV
PermitRootLogin no
PubKeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
PasswordAuthentication no
ChallengeResponseAuthentication no
GSSAPICleanupCredentials no
UsePAM yes
X11Forwarding yes
UsePrivilegeSeparation sandbox
AcceptEnv LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES
AcceptEnv LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT
AcceptEnv LC_IDENTIFICATION LC_ALL LANGUAGE
AcceptEnv XMODIFIERS
Subsystem sftp /usr/libexec/openssh/sftp-server
(RSA公開鍵)
#!/bin/bash
## startup_nuxt_dev.sh
set -x
readonly REPO_NAME="${プロジェクト名}"
readonly REPO_URL="${Githubのトークン+URL}"
readonly GIT_MAIL="${Gitするときのmailアドレス}"
readonly GIT_USER="${Gitするときのユーザ名}"
git clone $REPO_URL
git config --global user.email "$GIT_MAIL"
git config --global user.name "$GIT_USER"
cd $REPO_NAME
yarn install
npm run dev
ビルドと起動
やっと完成しました!では実行していきましょう。
docker-compose.ymlを設置したディレクトリで
docker-compose build
なにごともなければ、エラーが出ずに終了するはずです
docker-compose up
かっこよさげなインジケータとともに起動がはじまります。
初回はかなり時間がかかりますが、2回目からはそこそこ高速です。
#Wordpress初期設定
VPN接続し、http://192.168.1.254:3080
などでwordpressコンテナに直接続すればWordpress初期設定画面が表示されるはずです。
適当に設定した後、WP APIのプラグインをインストールします。
- wordpress初期セットアップ
- [WP REST API Controller]プラグインのインストール
- [パーマリンク設定] を [数字ベース] に変更
-
http://192.168.1.254:3000/wp-json/wp/v2/posts
などにアクセスしJSONが見れることを確認
その他拡張エディタなどお好みがあれば追加してください。
#開発環境初期設定
Nuxtでプロジェクトを作成しましょう。
git push します
#コンテナ群再起動
これですべてのファイルがそろったはずです。コンテナを終了し、再度起動します
[user01@hostname ~]# docker-compose down
[user01@hostname ~]# docker-compose up -d #-dでバックグラウンド実行
#動作確認
###Webアクセス
しばらくして起動したころになったら、Webアクセスして各種コンテナ内サービスがみれるかを確認します
- グローバルアドレスでhttpへアクセスし、httpsへ転送されてNuxtの開始ページがみれる
- グローバルアドレスでhttpsへアクセスし、Nuxtの開始ページがみれる
- VPNローカルアドレスでhttpへアクセスし、wordpressがみれる
- VPNローカルアドレスでhttpへアクセスし、開発用コンテナのNuxtの開始ページがみれる
###Wordpress接続確認
コンテナ内部で他コンテナの情報がとれるかを確認します
[user01@hostname ~]# docker exec -it webapp_dev bash
[root@webapp_dev]# curl http://wordpress:80/wp-json/wp/v2/posts
WordpressのHello World記事のJSONとかが取得できていればOKです
VSCodeからssh
- VSCodeにRemoteSSHエクステンションをインストール
- settingから接続configを記述
- 接続
何度かパスフレーズを聞かれるので入力します。SSH Agentの警告がでた場合、いれてもいいですし無しでもできます。
SSL認証鍵更新
期限に余裕があるので更新はスキップされますが、動作していればOKです。
[user01@hostname ~]# docker run --rm -p 80 -v "/etc/letsencrypt:/etc/letsencrypt" certbot/certbot certonly --webroot -w /etc/letsencrypt -d ${あなたのドメイン} --agree-tos --force-renew
#この作業でつかう確認コマンド集
#コンテナをすべて停止する
docker-compose down
#コンテナイメージ一覧
docker image ls
#稼働中コンテナ一覧
docker ps
#コンテナ内のログ
docker logs ${コンテナ名}
#よくあるエラー
###コンテナが起動しない(ポートアサインエラー)
コンテナのPortsに外部IPアドレスまで指定した場合、コンテナ起動時にそのIFが存在していないと起動できないようです。portsに仮想IF/IPを指定した場合、仮想IFが存在する状態で(VPN接続するなど)docker-compose up
する必要があります
###mysqlでmysql_updateしろというログがでている
mariadbでは最新版でも出なかったのですが、mysqlを使ってみた時に遭遇しました。
mysql_upgrade -u ${mariasqlのユーザ} -p${mariadbのパスワード}
を実行してください
コンテナ起動時ではなく、イメージがある限り1度実行すれば大丈夫なようです。
###nginxの対象ポートじゃないのに勝手にhttpsに転送される
ChromeかFirefoxを使用している場合、ブラウザ側の挙動として、一度httpsアクセスしたサイトは強制的にhttpsにリダイレクトします。リセットしてからアクセスしてください。このへんのテストはEdgeちゃんが唯一輝く場面かと思います・・・。
###wordpressがみれない
wordpressは初期設定した際にいろいろ自動でやってくれるので、一度ドメイン側からwordpressにアクセスして初期設定をしてしまうとIPアドレス直打ちではみれなくなったります。wordpresのディレクトリとDBイメージを削除してから再度実行しましょう
###npm run devやnpm buildができない
nuxtプロジェクト開始時の設定でnpmにした場合は、npm
で、yarnにした場合はyarn start
にしてください
###Teraterm等ではsshできるのにVSCodeでsshできない
C:\Users\${USERNAME}\.ssh\known_hosts
にゴミがのこっている可能性があります。中身をすべて削除してください(ファイルは消さないでください)
#さいごに?
おれたちはようやくのぼりはじめたばかりだからな・・・このはてしなく遠いコンテナ坂をよ・・・。
ということで、コンテナ間通信あり、永続化あり、専用コンテナあり、ほぼVMコンテナありと、一通りすべて体験できる地獄コンテナ群でとても勉強になりました。
dockerについてはむかし**「ガチなドッカーイメージをプルっちまうとマジ1発でコンテナードな環境がよォ!」**てきなかんじのことを聞いたので、じぶんのような雑魚でもセキュアな環境が一発でポワワ・・・な魔法アイテムだと思っていたのですが、使ってみると「結局NWは複雑になるし相変わらずconfをショドーする力いるしDockerfileもほぼshellだしshell力がさらに重要なんじゃ・・・?」とかおもわなくもないです。
ゆくゆくはDockerネットワークの挙動を把握しつつ、環境変数も活かして各種confも定数なしで記述したいところです。
次回はやっとNuxt.jsをさわってみます。ながかった・・・。