20210120のdockerに関する記事は5件です。

Docker-proxyでらくらくにIPv6対応しようとしたらがいつの間にか出来なくなっていた件

(自分用の備忘録)

やりたいこと

デュアルスタックのDockerホスト上で、Dockerプロキシを利用してIPv4シングルスタックのDockerコンテナでIPv6のサービスをしたい。

共通環境

Ubuntu 20.04

# cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=20.04
DISTRIB_CODENAME=focal
DISTRIB_DESCRIPTION="Ubuntu 20.04.1 LTS"
# uname -a
Linux server 5.8.0-36-generic #40~20.04.1-Ubuntu SMP Wed Jan 6 10:15:55 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

期待している挙動

以下のようなdocker-composeで適当なDockerコンテナを起動し、portsでポートフォワーディングを実施する。

version: '3'
services:
  grafana:
    image: grafana/grafana
    ports:
    - 3000:3000

コンテナ起動後、該当のポートフォワーディングを行うdocker-proxyプロセスが同時に立ち上がる。

root      120970  0.0  0.0 475320  3912 ?        Sl   18:27   0:00 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 3000 -container-ip 172.18.0.4 -container-port 3000

  • IPv4のトラフィック iptablesに以下のようなDNATルールが登場するため、docker-proxyプロセスに達する前に処理される。
# iptables -t nat -L DOCKER
Chain DOCKER (2 references)
target     prot opt source               destination
RETURN     all  --  anywhere             anywhere
RETURN     all  --  anywhere             anywhere
DNAT       tcp  --  anywhere             anywhere             tcp dpt:3000 to:172.18.0.3:3000

ちなみに返りのパケットはPOSTROUTINGでIPマスカレードされる

# iptables -t nat -L POSTROUTING 

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination
MASQUERADE  all  --  172.17.0.0/16        anywhere
MASQUERADE  all  --  172.18.0.0/16        anywhere
MASQUERADE  tcp  --  172.18.0.3           172.18.0.3           tcp dpt:3000
MASQUERADE  tcp  --  172.18.0.4           172.18.0.4           tcp dpt:8888

  • IPv6のトラフィック IPv6の場合IPv4とは異なりDNATのルールが挿入されず、上述docker-proxyプロセスがL4〜レベルで変換を実施する。

実際の挙動

これまで

確認したバージョン: Docker version 19.03.14, build 5eb3275d40

TypeIPv6になっており、IPv4/IPv6の両方でLISTENすることがわかる。

参考:
netstatやlsofでLISTENしているアドレスポートが IPv6と表示されてもIPv4でアクセスできる罠

# lsof -i:3000
COMMAND      PID USER   FD   TYPE  DEVICE SIZE/OFF NODE NAME
docker-pr 120970 root    4u  IPv6 1065660      0t0  TCP *:3000 (LISTEN)

最近

確認したバージョン: Docker version 20.10.2, build 2291f61

TypeIPv4になっており、IPv4でしかLISTENしていない。
そのためIPv6でサービス提供が行えていないない

# lsof -i:3000
COMMAND     PID USER   FD   TYPE  DEVICE SIZE/OFF NODE NAME
docker-pr 14845 root    4u  IPv4 6231154      0t0  TCP *:3000 (LISTEN)

対処方法

Docker versionを19.0代(多分 未検証)未満にすると以前までの挙動に戻る。
公式ドキュメントを参照して古いバージョンのdocker-ce, docker-ce-cliをinstallする。

Install Docker Engine

- installできるdocker-ceのバージョンを確認

# apt-cache madison docker-ce
 docker-ce | 5:20.10.2~3-0~ubuntu-focal | https://download.docker.com/linux/ubuntu focal/stable amd64 Packages
 docker-ce | 5:20.10.1~3-0~ubuntu-focal | https://download.docker.com/linux/ubuntu focal/stable amd64 Packages
 docker-ce | 5:20.10.0~3-0~ubuntu-focal | https://download.docker.com/linux/ubuntu focal/stable amd64 Packages
 docker-ce | 5:19.03.14~3-0~ubuntu-focal | https://download.docker.com/linux/ubuntu focal/stable amd64 Packages

  • 新しいdocker-ceをuninstall
# apt-get remove docker docker-engine docker.io containerd runc
  • 古いdocker-ce, docker-ce-cliをinstall
# apt-get install docker-ce=<VERSION_STRING> docker-ce-cli=<VERSION_STRING> containerd.io

所感と今後

このような仕様変更が行われた経緯に関してはま調べられていないが、めっちゃ困る

そもそもIPv4はiptablesのDNATで変換が入るのでプロキシ要らないようになっているのに、なぜTYPE: IPv4でdocker-proxyはLISTENするのか... なんならTYPE: IPv6でLISTENしてもIPv4でサービス提供できるのに...

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Docker Network 第3章】-net = hostオプションを理解する

image.png

シリーズ

【Docker Network 第1章】
【Docker Network 第2章】

はじめに

コンテナは現在、あらゆる開発サイクルにおいて必要とされている機能になっています。 コンテナネットワークがどのように機能するかを理解することは私たちにとって不可欠であります。これは、サービス通信の観点から重要であるだけでなく、インフラストラクチャセキュリティの重要な側面を形成しているからという理由でもあります。

この記事では、Dockerコンテナーで使用できるさまざまなネットワークモードについて簡単に学習し、ホストモードネットワークについて詳しく解説していきます。

ネットワークモードの概要

None

Noneは、コンテナがネットワークスタックを受信するという点で単純ですが、外部ネットワークインターフェイスがありません。 その代わりに、ループバックインターフェイスを受信します。 rktコンテナプロジェクトとDockerコンテナプロジェクトはどちらも、NoneまたはNullネットワークが使用されている場合に同様の動作を提供します。 コンテナネットワーキングのこのモードには、コンテナのテスト、後のネットワーク接続のためのコンテナのステージング、外部通信を必要としないコンテナへの割り当てなど、さまざまな用途があります。

ブリッジモード

Linuxブリッジは、同じホスト上のコンテナーが通信できるホスト内部ネットワークを提供しますが、各コンテナーに割り当てられたIPアドレスはホストの外部からアクセスできません。 ブリッジネットワーキングは、シングルホストネットワーキングを提供するNATおよびポートマッピングにiptablesを活用します。 ブリッジネットワークはデフォルトのDockerネットワークタイプ(つまり、docker0)であり、仮想ネットワークインターフェイスペアの一方の端がブリッジとコンテナの間に接続されます。

作成フローの例を次に示します。

  1. ホストにブリッジがプロビジョニングされます。
  2. 各コンテナの名前空間は、そのブリッジ内でプロビジョニングされます。
  3. コンテナのethXは、プライベートブリッジインターフェイスにマッピングされます。
  4. NATを使用したIptablesは、各プライベートコンテナとホストのパブリックインターフェイスの間のマッピングに使用されます。

NATは、ホストを超えた通信を提供するために使用されます。 ブリッジネットワークは、ポートの競合の問題を解決し、1つのホストで実行されているコンテナにネットワークの分離を提供しますが、NATの使用に関連するパフォーマンスコストがあります。

ホストモード

このアプローチでは、新しく作成されたコンテナーがそのネットワーク名前空間をホストと共有し、より高いパフォーマンスを提供し、NATの必要性を排除します。 ただ、欠点としてポートの競合が発生します。 コンテナはホストのすべてのネットワークインターフェースにアクセスできますが、特権モードで展開されていない限り、コンテナはホストのネットワークスタックを再構成しないことがあります。

ホストネットワークは、Mesos内で使用されるデフォルトのタイプです。 つまり、フレームワークでネットワークタイプが指定されていない場合、新しいネットワーク名前空間はコンテナではなくホストネットワークに関連付けられます。 ネイティブネットワークと呼ばれることもあるホストネットワークは、概念的に単純であり、理解、トラブルシューティング、および使用が容易になります。

オーバーレイ

オーバーレイはネットワークトンネルを使用して、ホスト間で通信を提供します。これにより、あるホストから次のホストにネットワークサブネットをトンネリングすることで、コンテナを同じマシン上にあるかのように動作させることができます。本質的には、1つのネットワークを複数のホストにまたがっています。仮想拡張可能ローカルエリアネットワーク(VXLAN)など、多くのトンネリングテクノロジが存在します。

VXLANは、マルチホストネットワークが1.9リリースでネイティブ機能として導入されたDockerlibnetworkに最適なトンネリングテクノロジーです。この機能の導入により、DockerはHashiCorpのSerfをゴシッププロトコルとして活用することを選択しました。これは、ネイバーテーブルの交換と収束時間の効率性のために選択されました。

他のトンネリングテクノロジーのサポートが必要な場合は、Flannelが最適です。 udp、vxlan、host-gw、aws-vpc、またはgceをサポートしており、クラウドプロバイダーのトンネルタイプごとに、アカウントまたは仮想プライベートクラウド(VPC)専用に、プロバイダーのルーティングテーブルにルートが作成されます。パブリッククラウドのサポートは、とりわけ、オーバーレイがハイブリッドクラウドのユースケースに最適に対応し、パブリックポートを開かなくてもスケーリングと冗長性を提供することを考えると、オーバーレイドライバーにとって特に重要です。

マルチホストネットワークでは、Dockerデーモンを起動するときに追加のパラメーターと、Key-Valueストアが必要です。一部のオーバーレイは、分散キー値ストアに依存しています。コンテナオーケストレーションを行っている場合は、分散型のKey-Valueストアがすでに存在します。

オーバーレイは、ホスト間の通信の課題に焦点を当てているため、2つの異なるオーバーレイネットワークに接続されている同じホスト上のコンテナは、相互にセグメント化されているためローカルブリッジを介して相互に通信できません。

アンダーレイ

アンダーレイネットワークドライバーは、ホストインターフェイス(つまり、eth0の物理ネットワークインターフェイス)を、ホスト上で実行されているコンテナーまたはVMに直接公開します。 このようなアンダーレイドライバーの2つは、メディアアクセス制御仮想ローカルエリアネットワーク(MACvlan)とインターネットプロトコルVLAN(IPvlan)です。 MACvlanおよびIPvlanドライバーの操作と動作は、ネットワークエンジニアには非常によく知られています。 どちらのネットワークドライバーも、ブリッジネットワークよりも概念的に単純であり、ポートマッピングの必要性を排除し、より効率的です。 さらに、IPvlanにはL3モードがあり、多くのネットワークエンジニアの共感を呼んでいます。 ほとんどのパブリッククラウドの制限(または機能の欠如)を考えると、アンダーレイは、オンプレミスのワークロード、セキュリティ上の懸念、トラフィックの優先順位、またはコンプライアンスに対処する必要がある場合に特に役立ち、ブラウンフィールドでの使用に最適です。 アンダーレイネットワーキングでは、VLANごとに1つのブリッジが必要になる代わりに、サブインターフェイスごとに1つのVLANが可能になります。

ホストモードネットワークの詳細を理解する

ホストモードは非常に単純に見えますが、展開するときに留意する必要のある項目がいくつかあります。 例を見て、私たちが話していることを確認しましょう。 まず、ホスト上で基本的なWebコンテナを開始しましょう。

docker run -d --name=web1 --net=host vaibhavthakur/docker:webinstance1

dockerrunコマンドで「–net = host」フラグを渡しています。 また、ポートマッピングを指定していません。 イメージがダウンロードされると、dockerはイメージを「web」と呼ばれるコンテナとして実行します。 このコンテナをデーモンとして実行するようにdockerに指示したので、次はコンテナのbashシェルに接続しましょう。


docker exec -it web1 /bin/bash

これにより、コンテナ内のシェルに移動し、コンテナで使用可能なすべてのネットワークアダプタを確認する必要があります。 以下に示すように行うことができます。

root@docker-1:~# docker exec -it web1 /bin/bash
[root@docker-1 /]# ifconfig
docker0   Link encap:Ethernet  HWaddr 02:42:9F:DE:3C:3C  
          inet addr:172.17.0.1  Bcast:172.17.255.255  Mask:255.255.0.0
          inet6 addr: fe80::42:9fff:fede:3c3c/64 Scope:Link
          UP BROADCAST MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:2 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:0 (0.0 b)  TX bytes:180 (180.0 b)

ens4      Link encap:Ethernet  HWaddr 42:01:0A:80:0F:E8  
          inet addr:10.128.15.232  Bcast:0.0.0.0  Mask:255.255.255.255
          inet6 addr: fe80::4001:aff:fe80:fe8/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1460  Metric:1
          RX packets:99119 errors:0 dropped:0 overruns:0 frame:0
          TX packets:10016 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:236101229 (225.1 MiB)  TX bytes:881725 (861.0 KiB)

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:336 errors:0 dropped:0 overruns:0 frame:0
          TX packets:336 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:35505 (34.6 KiB)  TX bytes:35505 (34.6 KiB)

ここで、コンテナを終了し、ホストで同じコマンドを実行する必要があります。

root@docker-1:~# ifconfig
docker0: flags=4099&lt;UP,BROADCAST,MULTICAST&gt;  mtu 1500
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255
        inet6 fe80::42:9fff:fede:3c3c  prefixlen 64  scopeid 0x20&lt;link&gt;
        ether 02:42:9f:de:3c:3c  txqueuelen 0  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 2  bytes 180 (180.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

ens4: flags=4163&lt;UP,BROADCAST,RUNNING,MULTICAST&gt;  mtu 1460
        inet 10.128.15.232  netmask 255.255.255.255  broadcast 0.0.0.0
        inet6 fe80::4001:aff:fe80:fe8  prefixlen 64  scopeid 0x20&lt;link&gt;
        ether 42:01:0a:80:0f:e8  txqueuelen 1000  (Ethernet)
        RX packets 99198  bytes 236116665 (236.1 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 10074  bytes 888616 (888.6 KB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73&lt;UP,LOOPBACK,RUNNING&gt;  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10&lt;host&gt;
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 336  bytes 35505 (35.5 KB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 336  bytes 35505 (35.5 KB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

root@docker-1:~# 

コマンドの出力はまったく同じであることがわかります。 これは、ホストIPを使用してコンテナにアクセスできる必要があることを意味します。 クラウドプロバイダーを使用している場合、ポート80で受け入れるファイアウォールルールがマシンで有効になっていることに注意してください。

インスタンスのパブリックIPでサービスにアクセスしてみましょう。
web1コンテナ内で実行されているサービスからの応答を確認できます。

image.png

コンテナの実行中にポートマッピングを提供せず、ホストノードネットワークを明示的に指定したため、ポート80でコンテナ内で実行されているApacheサーバーにアクセスできることに注意してください。

では、別のことを試してみましょう。 このホストでホストモードネットワークを使用して同じサービスの別のコンテナを実行し、何が起こるかを確認します。

docker run -it --name web2 --net=host vaibhavthakur/docker:webinstance2

注意深くみてみると、コンテナは起動すらしませんでした。 ログを見てみましょう。

root@docker-1:~# docker run -it --name web2 --net=host vaibhavthakur/docker:webinstance2
Unable to find image vaibhavthakur/docker:webinstance2' locally
webinstance2: Pulling from vaibhavthakur/docker
Image docker.io/jonlangemak/docker:webinstance2 uses outdated schema1 manifest format. Please upgrade to a schema2 image for better future compatibility. More information at https://docs.docker.com/registry/spec/deprecated-schema-v1/
a3ed95caeb02: Already exists 
3a5831f32025: Already exists 
1070eeab2b98: Already exists 
86ba7b11cc42: Already exists 
8ea0f21db18b: Already exists 
772c76531fbf: Already exists 
04120008298a: Already exists 
6a221adc032a: Already exists 
4a835ccd3c00: Already exists 
78e705e491cf: Already exists 
c343940bb393: Already exists 
663a5158a2b7: Already exists 
09cf647f3fa5: Pull complete 
Digest: sha256:62883de5c6d7786ec4267b8a0ccb9b104dea7523c9db934ecb911b78a5d983ee
Status: Downloaded newer image for vaibhavthakur/docker:webinstance2
2019-12-26 04:57:29,606 CRIT Supervisor running as root (no user in config file)
2019-12-26 04:57:29,619 INFO supervisord started with pid 1
2019-12-26 04:57:29,621 INFO spawned: 'httpd' with pid 6
2019-12-26 04:57:29,664 INFO exited: httpd (exit status 1; not expected)
2019-12-26 04:57:29,665 INFO received SIGCLD indicating a child quit
2019-12-26 04:57:30,668 INFO spawned: 'httpd' with pid 7
2019-12-26 04:57:30,707 INFO exited: httpd (exit status 1; not expected)
2019-12-26 04:57:30,707 INFO received SIGCLD indicating a child quit
2019-12-26 04:57:32,711 INFO spawned: 'httpd' with pid 8
2019-12-26 04:57:32,749 INFO exited: httpd (exit status 1; not expected)
2019-12-26 04:57:32,750 INFO received SIGCLD indicating a child quit
2019-12-26 04:57:35,755 INFO spawned: 'httpd' with pid 9
2019-12-26 04:57:35,796 INFO exited: httpd (exit status 1; not expected)
2019-12-26 04:57:35,796 INFO received SIGCLD indicating a child quit
2019-12-26 04:57:36,798 INFO gave up: httpd entered FATAL state, too many start retries too quickly

したがって、これは、エントリポイントプロセスがすぐに終了したため、コンテナが起動できなかったことを明確に示しています。 もう少し深く掘り下げて、コンテナの中に入りましょう。

root@docker-1:~# docker exec -it web2 /bin/bash
[root@docker-1 /]# service httpd status
httpd dead but subsys locked
[root@docker-1 /]# service httpd start
Starting httpd: (98)Address already in use: make_sock: could not bind to address [::]:80
(98)Address already in use: make_sock: could not bind to address 0.0.0.0:80
no listening sockets available, shutting down
Unable to open logs
                                                           [FAILED]

最初にhttpdサービスのステータスを確認したところ、停止していることがわかりました。 次に、起動しようとしましたが、ポート80にバインドできなかったため、再度失敗しました。もう一度テストを実行して、ポート80のステータスを確認してみましょう。

[root@docker-1 /]# netstat -plnt
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address               Foreign Address             State       PID/Program name   
tcp        0      0 127.0.0.53:53               0.0.0.0:*                   LISTEN      -                   
tcp        0      0 0.0.0.0:22                  0.0.0.0:*                   LISTEN      -                   
tcp        0      0 :::80                       :::*                        LISTEN      -                   
tcp        0      0 :::22                       :::*                        LISTEN      -                   
[root@docker-1 /]# 

ポート80が何らかのプロセスによって占有されていることがはっきりとわかります。このプロセスは、web1という名前のコンテナです。 したがって、web2コンテナがweb1が実行されているのと同じホスト上のポート80にバインドしようとしたため、失敗したと結論付けて良いでしょう。 それでは、web1コンテナを強制終了し、web2コンテナでhttpdサービスを開始してみましょう。

web1を強制終了した後、web2でhttpdを起動し、成功しました。

[root@docker-1 /]# service httpd start
Starting httpd:                                            [  OK  ]
[root@docker-1 /]# 

最後のテストとして、ポート80でインスタンスにアクセスしましょう。

image.png

web2コンテナ内で実行されているサービスからの応答を確認できました。

まとめ

ネットワークトラフィックはdocker0ブリッジとiptablesポートマッピングを通過する必要がないため、ホストモードネットワークはコンテナのネットワークパフォーマンスを向上させることを理解することが重要です。 この記事で、ホストモードネットワークとは何か、および他のネットワークオプションとの違いについて明確に説明してきました。 ご不明な点がございましたら、お気軽にお問い合わせください。

コンテナの監視を検討している場合は、MetricFireを介してHosted Prometheusで監視できます。 こちらから無料トライアルにサインアップするか、デモを予約して直接お問い合わせください。 詳細については、Dockerネットワークに関する3部構成のシリーズをご覧ください。このシリーズでは、さまざまな種類のDockerネットワークについて詳しく説明しています。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker と開発環境構築の忘備禄

初学者だが、今後もノートパソコンなど環境構築することになるので、メモをする。
Vagrant 2.2.14
Ubuntu 14.04.6 LTS
Docker version 1.6.2

勉強していたら講師が使用しているコマンド実行してエラー。
動画通りにいかない。調べたら、私の環境が古い。
Dockerのコマンドが新しく変わっている。
バージョンUPにはVagrant 上のUbuntu 14.04.6 LTSからUbuntu 16.04.7 LTSにアップグレードする必要がある。
OSアップデート

Ubuntu 14.04LTSから16.04.6 LTSにアップデートしてみた
を参考にアップグレード
Dockerのバージョンを別に上げると思っていたら一緒にアップグレードできた。
最新は
Dcoker version 18.09.7

これで、新しいDockerコマンドが実行できるようになった。

Vagrant上でVSCodeを使いたいので、VS codeのインストールと設定が必要。

「Vagrant上のファイルをVSCodeで編集する」 For Windowsを参考にインストール設定をする。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【いまさら】Docker Compose で Redmine しよう with Let's Encrypt

やりたいこと

  • Redmine を使って個人のタスク管理 (勉強、家事、趣味) などをしたい
  • お金はかけたくない、イニシャルコストは 10K 程度、ランニングは月々数百円 くらいが良い

やろうと思ったこと

wrike も考えた

無料プランではタイムトラック機能が使えないらしい。
何に時間を使ってるかも管理したいので、残念。

planio を使おうと思った

無料プランではタグを使うためのプラグインを入れられない。有料プランは個人で使うには高すぎる。

クラウドにホストしようかと思った

bitnami を使ってさくっとクラウドにホストしようかととも考えたが、ランニングコストがアホほどかかる。
bitnami の必須構成で組んでも月々数千円かかる。

やることになったこと

自宅サーバを立てる

今どきではない感が半端ないが、クラウド() が自分のユースケースにはあっていないため、仕方がなく自分で運用保守することにした。
筐体は少電力省スペースの Raspberry Pi 4 4GB を購入した。どういうケースにしようとか SD カードどうしようとか考えるのが面倒なので、日本の販売代理店のスターターキットを買うことにした。

数年前に買ったラズパイ3はメモリ 1GB だったのに、やっぱりムーアの法則ってあるんだなぁ...。

Docker Compose を使う

今どきならk8s とかでしょ!と思うんですが、全然、勉強をしていないので、Web に投稿されている手順や設定ファイルを読み解くことができず...。なにか変えたくなった時に手も足も出なくなりそう....。

コンテナの構成は、リバースプロキシ、AP サーバ (Redmine)、DB サーバ (MariaDB) のそれっぽい構成にする。

オレオレ自己証明書を卒業する

いつの間にやら個人でも気軽に無料で(!) SSL 証明書を取得できるような世の中になっていた。Let’s Encrypt という認証局である。実際には Certbot という ACME クライアントを使用することになる。

やったこと

ネットワークの事前設定

  • DDNS へのドメインの登録
  • ルータのポートフォワーディングの設定

OS インストールから Docker Compose のインストールまで

基本的に他の Qiita の記事の通りの実施で問題ないです。
地味にネットワークの設定ファイルの変更が初回起動の際に反映されておらず、netplan の設定ファイルの保存先を探したり、自分で netplan のコマンドを実行したりした。なんだよ netplan って。

Redmine を動かす (やっと本題)

設定のパクリ元

ディレクトリ構成

ディレクトリ、ファイルの配置は以下の通り。

./
├── docker-compose.yml
├── redmine
│   ├── config.ru
│   ├── plugins
│   └── themes
└── reverse_proxy
    ├── Dockerfile
    ├── default.conf.template
    └── ssl

Redmine の設定をする

docker-compose.yml の内、AP サーバ (Redmine) の部分は以下の通り。

x-DEFINE: &APP_RELATIVE_URL_ROO
  /redmine

x-DEFINE: &DB_PASSWORD
  redmine_passward

x-DEFINE: &TIME_ZONE
  Asia/Tokyo

(略)

  app:
    image: redmine:latest
    container_name: app
    ports:
      - 3000:3000
    volumes:
      - ./redmine/plugins:/usr/src/redmine/plugins
      - ./redmine/themes:/usr/src/redmine/public/themes
      - ./redmine/config.ru:/usr/src/redmine/config.ru
    environment:
      TZ: *TIME_ZONE
      REDMINE_DB_MYSQL: db
      REDMINE_DB_PASSWORD: *DB_PASSWORD
      RAILS_RELATIVE_URL_ROOT: *APP_RELATIVE_URL_ROO
    depends_on:
      - db
    restart: always

プラグインやテーマは必要に応じてボリュームでマッピングしているディレクトリに格納するようにする。
config.ru はサブディレクトリ運用する (例えば https://my.home.com/redmine でアクセスする) ために変更が必要な設定ファイルです。変更後内容は以下の通り。RAILS_RELATIVE_URL_ROOT の値は docker-compose.yml で定義している。

# This file is used by Rack-based servers to start the application.
require ::File.expand_path('../config/environment',  __FILE__)
map ENV['RAILS_RELATIVE_URL_ROOT'] || '/' do
    run Rails.application
end

MariaDB の設定をする

docker-compose.yml の 内、DB サーバ (MariaDB) の部分は以下の通り。特筆すべき点は特にない。

x-DEFINE: &DB_PASSWORD
  redmine_passward

x-DEFINE: &TIME_ZONE
  Asia/Tokyo

(略)
  db:
    image: mariadb:latest
    container_name: db
    ports:
      - 3306:3306
    environment:
      MYSQL_ROOT_PASSWORD: *DB_PASSWORD
      MYSQL_DATABASE: redmine
      TZ: *TIME_ZONE
    volumes:
      - ./data/db:/var/lib/mysql
    command: mysqld --character-set-server=utf8 --collation-server=utf8_unicode_ci
    restart: always

nginx + certbot の設定をする

リバースプロキシ (nginx) と ssl 証明書の取得 (certbot) の設定を行う。

docker-compose.yml の リバースプロキシの部分は以下の通り。

x-DEFINE: &APP_RELATIVE_URL_ROO
  /redmine

(略)

  reverse_proxy:
    container_name: reverse_proxy
    build:
      context: ./reverse_proxy
      network: host
      args:
        - CERTBOT_EMAIL=xxxxx@yyyyy.zzzzz #replace with your own email
        - DOMAIN_LIST=my.home.com                #replace with your own domains
    environment:
      APP_ROOT: *APP_RELATIVE_URL_ROO
    volumes:
      - ./reverse_proxy/default.conf.template:/etc/nginx/templates/default.conf.template
      - ./reverse_proxy/ssl:/etc/letsencrypt
    ports:
      - "80:80"
      - "443:443"
    restart: always

リバースプロキシのイメージは nginx の公式イメージではなく、自分でカスタムしたものをビルドする。
イメージビルド時に渡す二つのパラメータ CERTBOT_EMAIL DOMAIN_LIST は Let's Encrypt に登録する連絡用メールアドレスと ssl 証明書を取得する対象のドメイン名である。これらの値は docker-compose.yml から設定している。

environments で指定しているのは Redmine が動くサブディレクトリ名である。
volumes で指定しているのは nginx の設定ファイルと、ssl 認証の格納先である。

リバースプロキシの Dockerfile の内容は以下の通りである。

FROM nginx:latest
ARG CERTBOT_EMAIL=default@default.com
ARG DOMAIN_LIST

# Expose ports.
EXPOSE 80
EXPOSE 443

RUN  apt-get update \
      && apt-get upgrade -y \
      && apt-get install -y cron certbot \
      && certbot certonly --dry-run --standalone --agree-tos -m "${CERTBOT_EMAIL}" -n -d ${DOMAIN_LIST} \
#     && certbot certonly  --standalone --agree-tos -m "${CERTBOT_EMAIL}" -n -d ${DOMAIN_LIST} \
      && rm -rf /var/lib/apt/lists/* \
      && echo "@monthly /usr/local/bin/certbot renew --nginx >> /var/log/cron.log 2>&1" >/etc/cron.d/certbot-renew \
      && crontab /etc/cron.d/certbot-renew 
VOLUME /etc/letsencrypt

CMD [ "sh", "-c", "cron && ./docker-entrypoint.sh nginx -g 'daemon off;'" ]

細かな説明はパクリ元を参照願う。
パクリ元から変更しているのは、

  • certbot のインストールを apt で行うようにした。その影響でcertbot-auto ではなくなっている。
  • entorypoint.sh から 20-envsubst-on-templates.sh が呼ばれるように CMDを変更 (ダサいとは思っているが他に思いつかない)

default.conf.template の内容は以下の通りである。
(https の設定は certbot の成功を一度確認してからコメントを外す)

server {
    listen       80;
    server_name  localhost;

    #charset koi8-r;
    #access_log  /var/log/nginx/host.access.log  main;

    location ${APP_ROOT} {
        proxy_set_header X-Forwarded-Host $host:$server_port;
        proxy_set_header X-Forwarded-Server $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://app:3000${APP_ROOT}/;
    }

    # --- For CertBot ---
    location ^~ /.well-known/acme-challenge/ {
        root /usr/share/nginx/html/;
    }

    location = /.well-known/acme-challenge/ {
        return 404;
    }
    # -----------------

    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    #
    #location ~ /\.ht {
    #    deny  all;
    #}
}

#server {
#   listen 443;
#   server_name shop.nureteni.awa.co.jp;
#
#    ssl on;
#    ssl_certificate /etc/letsencrypt/live/${MY_DOMAIN_NAME}.cert;
#    ssl_certificate_key /etc/letsencrypt/live/${MY_DOMAIN_NAME}.cert.key;
#
#    location ${APP_ROOT} {
#
#    proxy_set_header X-Forwarded-Host $host:$server_port;
#    proxy_set_header X-Forwarded-Server $host;
#    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
#    proxy_pass http://app:3000${APP_ROOT}/;
#    }
#    # --- For CertBot ---
#    location ^~ /.well-known/acme-challenge/ {
#        root /usr/share/nginx/html/;
#    }
#
#    location = /.well-known/acme-challenge/ {
#        return 404;
#    }
#}

以上で docker-compose up -d で必要なコンテナが立ち上がる。
あとは https://my.home.com/redmineで Redmine にアクセスできる。

おわりに

細かいことに詰まることが多く、スキマ時間にやっていたけれども 2 週間近くの時間を消費することになった。
マイナーなユースケースだと思うが、この地球のどこかの人のためになればと思い、この記事を投稿した。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker php-apacheを自己証明書でSSL対応(自分メモ)

前回の記事でphp-apache+mysqlを作りましたが、今回はそれを自己証明書を作り、SSL対応するまでを書いていきます。
ゴールはchromeから警告なしで、アクセスできるようになるまでです。

環境

マシン macOS Catalina 10.15.7

構成

前回終了時こんな感じで終わり

任意のディレクトリ
  │
  ├── docker-compose.yml
  │
  ├── html/
  │   └── index.html
  │
  ├── mysql/
  │   ├── Dockerfile
  │   ├── data/
  │   ├── init/
  │   │   └── init.sql
  │   └── my.cnf
  │
  ├── php-apahce/
      ├── Dockerfile
      └── php.ini

今回はこんな感じになります。

任意のディレクトリ
  │
  ├── docker-compose.yml 更新
  │
  ├── html/
  │   └── index.html
  │
  ├── mysql/
  │   ├── Dockerfile
  │   ├── data/
  │   ├── init/
  │   │   └── init.sql
  │   └── my.cnf
  │
  ├── php-apahce/
      ├── Dockerfile 更新
      ├── ssl.conf 追加
      └── ssl/
          ├──ssl.key 追加
          └──ssl.crt 追加

構築

さっそく始めましょう

自己証明書の作成

まずは自己証明書を作りましょう
とりあえずディレクトリ作って移動

ksk@ksknoMacBook-Pro work % mkdir php-apache/ssl
ksk@ksknoMacBook-Pro work % cd php-apache/ssl

秘密鍵作成

ksk@ksknoMacBook-Pro ssl % openssl genrsa -out ssl.key 2048

Generating RSA private key, 2048 bit long modulus
............+++
.+++
e is 65537 (0x10001)

CSR作成

Common Nameだけ「localhost」と入力して、後全部Enter

ksk@ksknoMacBook-Pro ssl % openssl req -new -sha256 -key ssl.key -out ssl.csr
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) []:
State or Province Name (full name) []:
Locality Name (eg, city) []:
Organization Name (eg, company) []:
Organizational Unit Name (eg, section) []:
Common Name (eg, fully qualified host name) []:localhost
Email Address []:
error, no objects specified in config file
problems making Certificate Request

san.txt作成

最近の自己証明書でchromeの警告回避するには、これがいるとかいらないとか

ksk@ksknoMacBook-Pro ssl % echo "subjectAltName = DNS:localhost" > san.txt

証明書の作成

10年とかにしたら警告が出た気がするんで、1年にしました。

ksk@ksknoMacBook-Pro ssl % openssl x509 -req -sha256 -days 365 -signkey ssl.key -in ssl.csr -out ssl.crt -extfile san.txt

証明書の準備は完了

ssl.conf

ssl.confを前回作った既存のコンテナのssl.confを基にして作りたい為、とりあえずコンテナの状態確認

ksk@ksknoMacBook-Pro ssl % cd ../
ksk@ksknoMacBook-Pro php-apache % docker container ls -a
CONTAINER ID   IMAGE             COMMAND                  CREATED          STATUS          PORTS                                         NAMES
4c43e97e5c37   work_mysql        "docker-entrypoint.s…"   20 minutes ago   Up 20 minutes   0.0.0.0:3306->3306/tcp, 33060/tcp             db
35889b716286   work_php-apache   "docker-php-entrypoi…"   20 minutes ago   Up 20 minutes   0.0.0.0:8080->80/tcp                          web

webコンテナの方のコンテナIDを使いssl.confをローカルにコピー

ksk@ksknoMacBook-Pro php-apache % docker cp 35889b716286:/etc/apache2/sites-available/ssl.conf ./

ssl.conf編集
/etc/httpd/ssl/に配置するものとする。

ssl.conf
〜〜〜
SSLCertificateFile    /etc/httpd/ssl/ssl.crt
SSLCertificateKeyFile /etc/httpd/ssl/ssl.key
〜〜〜

Dockerfile編集

前回の記事からすると、
RUN mkdir -p /etc/httpd/ssl
以降を追記してます。

FROM php:7.4-apache
COPY ./php.ini /usr/local/etc/php/
RUN apt-get update
RUN apt-get install -y zip unzip vim libpng-dev libpq-dev
RUN docker-php-ext-install pdo_mysql

RUN mkdir -p /etc/httpd/ssl
RUN a2enmod ssl
COPY ./ssl.conf /etc/apache2/sites-available/ssl.conf
COPY ./ssl/ssl.key /etc/httpd/ssl/ssl.key
COPY ./ssl/ssl.crt /etc/httpd/ssl/ssl.crt
RUN a2ensite ssl

CMD ["/usr/sbin/apache2ctl", "-D", "FOREGROUND"]

docker-compose.ymlの編集

8443から443ポートに接続できるようにします

docker-compose.yml
version: '3'
services:
  php-apache:
    build: ./php-apache/
    volumes:
      - ./html:/var/www/html
    ports:
      - 8080:80
      - 8443:443
    container_name: web
  mysql:
    build: ./mysql/
    volumes:
      - "./mysql/data:/var/lib/mysql"
      - "./mysql/init:/docker-entrypoint-initdb.d"
    environment:
      - MYSQL_ROOT_PASSWORD=docker
      - MYSQL_DATABASE=mydb
      - MYSQL_USER=appuser
      - MYSQL_PASSWORD=appuser1
    container_name: db
    ports:
      - 3306:3306

これでとりあえず準備完了

再度build&起動

まず前のやつ残ってたら消します。

ksk@ksknoMacBook-Pro cd ../
ksk@ksknoMacBook-Pro work docker container stop web
ksk@ksknoMacBook-Pro work docker container rm web
ksk@ksknoMacBook-Pro work docker container stop db
ksk@ksknoMacBook-Pro work docker container rm db

build と 起動

ksk@ksknoMacBook-Pro work docker-compose build
ksk@ksknoMacBook-Pro work docker-compose up -d

起動確認

ksk@ksknoMacBook-Pro work % dodker container ls -a
CONTAINER ID   IMAGE             COMMAND                  CREATED          STATUS          PORTS                                         NAMES
4c43e97e5c37   work_mysql        "docker-entrypoint.s…"   20 minutes ago   Up 20 minutes   0.0.0.0:3306->3306/tcp, 33060/tcp             db
35889b716286   work_php-apache   "docker-php-entrypoi…"   20 minutes ago   Up 20 minutes   0.0.0.0:8080->80/tcp, 0.0.0.0:8443->443/tcp   web

証明書の登録

先ほどのssl.crtが入っているフォルダにアクセスして
qiita_20200118_01.png
これをダブルクリック

キーチェーンはシステムで追加を押してください。
qiita_20200118_02.png

キーチェーン開くと、先ほど登録した証明書がドメイン名で現れるので、ダブルクリックします。
「この証明書を使用する時」を「常に信頼」にすると全てが「常に信頼」になるので、それでこのウィンドウを閉じてください。
qiita_20200118_03.png

最後は画面にアクセスして確認
警告なしに、アドレスの横の鍵マークも不穏な空気を出さずにアクセスできました。
qiita_20200118_04.png

以上。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む