🐝

ポートの競合でApacheコンテナが起動できない

2024/08/03に公開

前置き

エンジニアに転職するため、企業の方に見てもらうオリジナルアプリを作成しています。

GitHub ActionsでEC2にSSHログインして、Dockerコンテナ(RailsコンテナとApacheコンテナ)を本番環境で起動することを試みています。

今回のエラーは、EC2にログインしてdocker-compose upでApacheコンテナを起動しようとして生じました。

deploy.yml
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 80Listen 8080に変更しました。

これにより、ホストのポート80をApacheコンテナのポート80にバインドする以下の設定が実行されるようになります。

docker-compose-prod.yaml
services:
  apache:
    image: httpd:latest
    ports:
      - 80:80

lsof -i :80の結果を見ると、COMMANDdocker-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