ポートの競合でApacheコンテナが起動できない
前置き
エンジニアに転職するため、企業の方に見てもらうオリジナルアプリを作成しています。
GitHub ActionsでEC2にSSHログインして、Dockerコンテナ(RailsコンテナとApacheコンテナ)を本番環境で起動することを試みています。
今回のエラーは、EC2にログインしてdocker-compose up
でApacheコンテナを起動しようとして生じました。
name: Deploy
on:
push:
branches: [ feature/deploy-docker-production ]
jobs:
test:
# 省略
deploy:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy in Amazon EC2
env:
# 省略
run: |
echo "$PRIVATE_KEY" > private_key && chmod 600 private_key
ssh -o StrictHostKeyChecking=no -i private_key ${USER_NAME}@${HOST_NAME} "
# 省略
echo ${SUDO_PASS} | sudo -S docker-compose -f docker-compose.yaml -f docker-compose-prod.yaml down -v &&
screen -dm echo RAILS_ENV=production ${SUDO_PASS} | sudo -S docker-compose -f docker-compose.yaml -f docker-compose-prod.yaml up -d --build &&
netstat -tuln | grep :80 &&
echo ${SUDO_PASS} | sudo -S lsof -i :80 &&
echo ${SUDO_PASS} | sudo -S docker ps
"
問題
デプロイログを見ると、Dockerデーモンからエラーレスポンスが返ってきています。
Apacheコンテナ「ocr_check_app_apache-1」のエンドポイント(接続窓口)で外部との接続に失敗しているようです。
80番ポートにきたリクエストをApacheコンテナにバインドしようとしたところ、80番ポートがすでに他のプログラムに使われていてバインドできないと言われました。
原因
ポート80の競合が起きています。
何のプロセスがポート80で動いているのかlsof -i :80
で調べてみると、ホスト(EC2)のApacheだと判明しました。
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
httpd 1983 root 3u IPv6 16114 0t0 TCP *:http (LISTEN)
httpd 1990 apache 3u IPv6 16114 0t0 TCP *:http (LISTEN)
httpd 1991 apache 3u IPv6 16114 0t0 TCP *:http (LISTEN)
httpd 1992 apache 3u IPv6 16114 0t0 TCP *:http (LISTEN)
httpd 3247 apache 3u IPv6 16114 0t0 TCP *:http (LISTEN)
前提として、今回のエラーはEC2にログインしてDockerコンテナを起動したときに発生しています。
そのため、ホストはEC2です。
また、すでにEC2にApacheをインストールしてあります。
Apacheの設定ファイル(httpd.conf)に以下の行があります。
Listen 80
この設定により、ホスト(EC2)の80番ポートにきたリクエストはホスト(EC2)のApacheが待ち受けています。
ホストのポート80が空いてないとApacheコンテナにバインドできない理由
なぜ今回ポート80の競合が起きたのでしょうか?
結論から言うと、1つのポートにつき1つのプロセス(プログラム)と決まっていて、ホストのポート80がホスト(EC2)のApacheに使われていたからです。
ホストのApacheとコンテナのApacheはそれぞれ独立したもので、同じ「Apache」という名前でも別のプロセスです。
ホストのポート80がホスト(EC2)のApacheと結びついた状態で、ポート80をApacheコンテナにバインドすると、1つのポートが2つのプロセスと結びつくのでそれは許されません。
ポート80とホストのApacheの結びつきを解消すれば、Apacheコンテナがポート80を使えるようになります。
解決した方法
ポートの競合を解消するには、ポート80とEC2のApacheとの結びつきを切り離します。
ホストのポート80をApacheコンテナにバインドさせるため、EC2のApacheはポート8080で設定することにしました。
具体的には、EC2のApacheの設定ファイル(httpd.conf)のListen 80
をListen 8080
に変更しました。
これにより、ホストのポート80をApacheコンテナのポート80にバインドする以下の設定が実行されるようになります。
services:
apache:
image: httpd:latest
ports:
- 80:80
lsof -i :80
の結果を見ると、COMMAND
がdocker-pr
に変わっているのでDockerコンテナに転送できているようです。
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
docker-pr 134316 root 4u IPv4 602025 0t0 TCP *:http (LISTEN)
docker-pr 134321 root 4u IPv6 602030 0t0 TCP *:http (LISTEN)
docker ps
の結果を見ると、ポート80がApacheコンテナにバインドされて起動していることが確認できました。
Apacheコンテナをポート8080にしない理由
ポート80の競合を解消するには、EC2のApacheはポート80のままにして、Apacheコンテナをポート8080でバインドさせる方法もありました。
ではなぜそれをやらないことにしたかというと、その方法だとユーザーがアクセスするときにポートを指定する必要があり(例:http://example.com:8080
)、ユーザーに余計な疑問(「この:8080
ってなんだろう?」)を持たせるのを避けるためです。
HTTP通信のデフォルトポートは80なので、ポート80ならhttp://example.com
でアクセスできます。
言い換えると、http://example.com
でアクセスすると自動的にポート80が使われます。
サービスのURIにポート番号が付いているのはユーザーにとって余計な違和感や疑問を与えるので、Apacheコンテナを非デフォルトポートにしないことにしました。
Discussion