DockerをCentOSで運用する際にコンテナ内部からhttps通信ができない問題の対策

# エラー内容：何が起きたか
コンパイル言語であるGoのコードや機能の改修を行いやすくするために、Dockerを用いてGoの環境構築を行っていたところコンテナ内部で謎のネットワークエラーが出た。

行った手順としてはまず、以下のようなDockerfileとdocker-compose.ymlを作成。

```docker:Dockerfile
FROM golang:latest

WORKDIR /path/to/go/project

COPY go.mod ./
COPY go.sum ./

RUN go mod download

COPY path/to/main.go ./

EXPOSE 8080

```


```docker:docker-compose.yml
version: '3'
services:
    app:
        container_name: go_container
        image: golang:latest
        tty: true
        ports:
            - "3000:3000"
        volumes:
            - /path/to/$GOPATH:/go
```

(※なお、**今回の事象はこれと全く同じ構成のdocker-composeでなくとも発生する。** 理由は後述)

docker-composeを実行

```
docker-compose up --build -d
```

すると、**go mod downlodのところで永遠に処理が終わらない。**

## 別のアプローチ
Dockerfileを以下のように変更

```docker:Dockerfile
FROM golang:latest

WORKDIR /path/to/go/project

COPY go.mod ./
COPY go.sum ./

RUN go get -u github.com/go-sql-driver/mysql

COPY path/to/main.go ./

EXPOSE 8080

```

go getで取得するモジュールはなんでもOK

これを実行すると以下のようなエラーが返ってくる。
`(前略) net/http: TLS handshake timeout`

つまりネットワークが途絶えてしまっている。
後の調査(内容割愛。curlとか)の結果、奇妙なことにhttps通信の場合にだけこのような事象が発生することがわかった。

## 原因：答えはスタート地点に
原因がわからず数日にわたって調べまくったところ、公式の最新バージョン(20.10.8)リリースノートに以下のような記述を発見。

IMPORTANT
Due to net/http changes in Go 1.16, HTTP proxies configured through the ~~ $HTTP_PROXY environment variable are no longer used for TLS (https://) connections. Make sure you also set an $HTTPS_PROXY environment variable for handling requests to https:// URLs.
Refer to the HTTP/HTTPS proxy section to learn how to configure the Docker Daemon to use a proxy server.

~~**go側のnet/httpの仕様変更に伴って環境変数 $HTTPS_PROXYも自分で設定しなきゃダメだよということ。**(かなり大雑把)
灯台下暗しとはまさにこのこと。
これならどのようなDockerfileを書いたところで同じ事象に遭遇するはず~~

~~とはいえ、設定するようなプロキシサーバがなかったため、squidを使用してホスト側にプロキシサーバを建てることに。~~

:::note alert 
原因はこのDockerの仕様ではなくOSのカーネル、あるいはクラウドサービス側のOS設定に原因がありそうだということがわかりました。誤情報すみません。
CentOSを使っているときにこういったネットワーク接続周り(iptablesやfirewalldなど)で動作がうまくいかないケースがあるようで、これらの内部での対応をあきらめてプロキシで無理やりネットワークを通していこうというのが以下の対策となります。よって対応策は同じです。
:::

## 対策：環境変数の設定とプロキシサーバの設置

### squidのインストール・設定

(僕の利用していた環境はcentos7なので、yumを利用して)squidをインストール

```
sudo yum install -y squid
```

/etc配下にある設定ファイルを編集

```squid_config:/etc/squid/squid.conf
#
# Recommended minimum configuration:
#

# Example rule allowing access from your local networks.
# Adapt to list your (internal) IP networks from where browsing
# should be allowed
acl localnet src 10.0.0.0/8	# RFC1918 possible internal network
acl localnet src 172.16.0.0/12	# RFC1918 possible internal network
acl localnet src 192.168.0.0/16	# RFC1918 possible internal network
acl localnet src fc00::/7       # RFC 4193 local private network range
acl localnet src fe80::/10      # RFC 4291 link-local (directly plugged) machines

acl SSL_ports port 443
acl Safe_ports port 80		# http
acl Safe_ports port 21		# ftp
acl Safe_ports port 443		# https
acl Safe_ports port 70		# gopher
acl Safe_ports port 210		# wais
acl Safe_ports port 1025-65535	# unregistered ports
acl Safe_ports port 280		# http-mgmt
acl Safe_ports port 488		# gss-http
acl Safe_ports port 591		# filemaker
acl Safe_ports port 777		# multiling http
acl CONNECT method CONNECT

# 以下2行を追記(多分コンテナの方だけでいい)
acl name(任意の名前1) src (DockerホストのIP)
acl name2(任意の名前2) src (DockerコンテナのIP)

#
# Recommended minimum Access Permission configuration:
#
# Deny requests to certain unsafe ports
http_access deny !Safe_ports

# Deny CONNECT to other than secure SSL ports
http_access deny CONNECT !SSL_ports

# Only allow cachemgr access from localhost
http_access allow localhost manager
http_access deny manager

# We strongly recommend the following be uncommented to protect innocent
# web applications running on the proxy server who think the only
# one who can access services on "localhost" is a local user
#http_access deny to_localhost

#
# INSERT YOUR OWN RULE(S) HERE TO ALLOW ACCESS FROM YOUR CLIENTS
#

# Example rule allowing access from your local networks.
# Adapt localnet in the ACL section to list your (internal) IP networks
# from where browsing should be allowed
# http_access allow localnet
http_access allow localhost

# 以下2行を追記(こちらも多分コンテナの方ry)
# (※name, name2はさきほど定義したもの)
http_access allow name 
http_access allow name2

# http_access allow all
# And finally deny all other access to this proxy
http_access deny all

# Squid normally listens to port 3128
http_port 3128

# Uncomment and adjust the following to add a disk cache directory.
#cache_dir ufs /var/spool/squid 100 16 256

# Leave coredumps in the first cache dir
coredump_dir /var/spool/squid

#
# Add any of your own refresh_pattern entries above these.
#
refresh_pattern ^ftp:		1440	20%	10080
refresh_pattern ^gopher:	1440	0%	1440
refresh_pattern -i (/cgi-bin/|\?) 0	0%	0
refresh_pattern .		0	20%	4320
```
(squidのデフォルトのポート番号は3128。今回はとくに変更しない。)


コンテナのIDがわからない場合は、コンテナ内部に入り以下のコマンドで確認

```
hostname -i
```
[参考:コンテナのIPを確認する方法](https://qiita.com/ponsuke0531/items/7e8e5081993a30afdc4a)

開始

```
sudo systemctl start squid
```
次はdocker側の設定

### Docker側、環境変数$HTTP(S)_PROXYの設定
[Docker公式の設定ページ](https://docs.docker.com/config/daemon/systemd/#httphttps-proxy)に従って環境変数を設定。

```
sudo mkdir -p /etc/systemd/system/docker.service.d
```

```docker_config:/etc/systemd/system/docker.service.d
Environment="HTTP_PROXY=(squidを設置したホストのIP):3128"
Environment="HTTPS_PROXY=(squidを設置したホストのIP):3128"
```

ついでに、dockerを利用するユーザーのホームディレクトにconfig.jsonを設置。
[公式ページの設定ガイドラインはこちら](https://matsuand.github.io/docs.docker.jp.onthefly/network/proxy/)


```docker_json:~/.docker/config.json
{
 "proxies":
 {
   "default":
   {
     "httpProxy": "(squidホスト):3128",
     "httpsProxy": "(squidホスト):3128"
   }
 }
}

```

書いたらDockerを再起動。

```
sudo systemctl restart docker
```
再起動するとコンテナが止まってしまうためコンテナをstartするのを忘れないように。
~~自分はそれで気づかずにホストでhttpsの検証してぬか喜びしてしまった~~

## 確認
コンテナ内部でcurlコマンドを使い検証。
alpine使ってる人とかでcurlできない人はインストールするか、apk updateでもかけてみるのがよし。

```
crul https://google.com
```

`<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>301 Moved</TITLE></HEAD><BODY>
<H1>301 Moved</H1>
The document has moved
<A HREF="https://www.google.com/">here</A>.
</BODY></HTML>`

HTMLが返ってきたらOK。
この状態になればgo getやgo mod downloadなどのコマンドも通る。


## 総括
~~**最新版(20.10.8)のDockerはコンテナ内部からhttps通信を行うためにプロキシが必要。**
20.10.7などの直前バージョンを使おうとしても、**他モジュールが20.10.8でくっついてきてしまったり、そもそものgoの仕様変更だったりという事情で結局この事象から逃れられなかった。**
また、最初からhttp通信はできていたが、$HTTP_PROXYのデフォルト値は見つけられなかった。(echoだと空行)~~

**OS起因でのネットワークエラーなので素直にCentOSをやめて別のOSを使うのが最善。それができない事情がある場合は上記のように無理やりプロキシで帳尻を合わせて対応すれば使うことはできます。
Goの場合はセオリーとはやり方が異なりますが、プロジェクトごとvolumeしておいてdocker-compose.yml側でビルドすればproxy経由でモジュールを取得できます。Dockerfileで同じことをやろうとすると、proxy利用のための環境変数を設定する前にイメージビルドが走るためモジュールの取得に失敗します。**

なお、CentOSであっても個人的に使っている環境ではこのような事象は起きず普通に使えることから提供しているサーバ会社の設定なども加味された場合に起こる特殊な現象であると考えられます。