昨日まで問題なく起動していたdockerのコンテナで、docker-compose upによる起動時に for xxx Cannot start service xxx: Ports are not available: listen tcp 0.0.0.0:50100: bind: An attempt was made to access a socket in a way forbidden by its access permissions. が表示されるようになったので、原因を調べてメモにしてみました。
他のプロセスにポートが占有されている場合
例えば HTTPの80やnodeの3000、mysqlの3306などは被りやすいため、ほかのプロセスで既に使用されている可能性があります。dockerのコンテナがそのポートを使用している場合は
ERROR: for mysql Cannot start service mysql: driver failed programming external connectivity on endpoint mysql (xxxxxxxxx): Bind for 0.0.0.0:3306 failed: port is already allocated
というようにわかりやすく出てくれるため、docker ps でポートのマッピングを確認すればよいですが、そうでない場合は netstat -anob や lsof -i などを使って、どのプロセスがポートを使用しているか調べる必要があります。
カーネルにポートが占有されている場合
ネットワーク通信においてエフェメラルポートという、動的に使用されるポート番号があるのですが、実はこのエフェメラルポートは49152から開始することが推奨されています。そのため、冒頭の50000番台のような49152から近いポート番号でエラーが発生した場合は、カーネルがエフェメラルポートとしてそのポートを使用しており衝突した可能性があります。その場合はnetstatやlsofでは確認できない場合があるので、
。その場合は、
netsh int ipv4 show dynamicport tcp
netsh int ipv4 show dynamicport udp
netsh int ipv6 show dynamicport tcp
netsh int ipv6 show dynamicport udp
で、IPV4/V6、TCP/UDPのそれぞれのエフェメラルポートの設定の確認と、
netsh int ipv4 set dynamicport tcp start=10000 num=1000
netsh int ipv4 set dynamicport udp start=10000 num=1000
netsh int ipv6 set dynamicport tcp start=10000 num=1000
netsh int ipv6 set dynamicport udp start=10000 num=1000
などのコマンドで、エフェメラルポートの開始ポート番号を変更し、実際にポートが使用できるかどうかを確認します。今回はこちらの対応方法で問題が解消し、コンテナが起動するようになりました。注意点としてTCPの設定ではなくUDPの方でポートが占有されているようでした。
最近行った変更というと、確かにUDP用のプログラムを検証するため、USB-LANモジュールのインストールや、Wiresharkのインストールを行ったのですが、何が直接作用したのかは不明です。
いずれにせよ、どのプロセスからもポートの使用が確認できない場合は、カーネル側のエフェメラルポートの設定を見直すことで現象が解決する可能性がありますので、その点を知っておくとよいと思います。