今一番楽なDockerのIPv6対応方法

# モチベーションと概要
- Dockerのコンテナでも簡単にIPv6でサービスしたい。
- docker composeでIPv4と同じノリでやりたい
- 色んな方法が乱立していてわからない
- 執筆現在で一番楽な方法をまとめた。
  - IPv6 NAPT を利用するので、それぞれの信条によっては相容れないかもしれない。

:::note warn
初稿時点(2023年)では`Experimental`な機能を利用しています。
新しめの機能を利用しているため、あなたが読んでる時には仕様が変わっている可能性があります。
:::



## 環境
```
# docker --version
Docker version 27.3.1, build ce12230
# uname -r
6.8.0-45-generic
# cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=24.04
DISTRIB_CODENAME=noble
DISTRIB_DESCRIPTION="Ubuntu 24.04.1 LTS"

```
- 改訂時点(2024/11)でlatestなversionのdocker

# 手順
後述する"自作bridgeでNAPT法"をベースに、dockerに最近実装された新しい機能を使って、自分でNATルールのケアをしなくて済むようにした...という内容。


## 1. `/etc/docker/daemon.json` : IPv6関連機能を有効化
```json:/etc/docker/daemon.json
{
    "ipv6": true,
    "ip6tables": true,
    "userland-proxy": false,
    "fixed-cidr-v6": "fd00:ace:cafe:beaf::/80"
}
```

### 項目の説明
- `IPv6`: `true`にしてコンテナがIPv6を利用するようにする。

[^ulaplobrem]: [Considerations For Using Unique Local Addresses
](https://datatracker.ietf.org/doc/draft-ietf-v6ops-ula-usage-considerations/)
[^6bone]: 例えば3ffe::/16 [6boneアドレス(3FFE::/16)の利用廃止について](https://www.nic.ad.jp/ja/topics/2006/20060529-01.html) 

- `ip6tables` : IPv4と同様にDNATによるポートフォワーディングを行う。
- `user-land-proxy`: ip6tablesによるDNATを使う場合は通常使われないので、無効化する。
- `fixed-cidr-v6`: default bridge(`docker0`)が使うIPv6 prefixを明示的に指定する場合がある。一般的には[ULA](https://datatracker.ietf.org/doc/html/rfc4193)の使用が推奨される。
しかしにULAはアドレス選択ポリシーでプライベートIPv4アドレスに負けるので[^ulaplobrem]、IPv4を無効化出来ない現状のdocker network実装ではIPv6シングルスタック環境で動作させる時に困る。[アドレス選択ポリシーをオーバライドすることも可能](https://memo.techack.net/linux/etc/gai.conf.html)ですが、IPv6インターネットで利用されてない別のアドレス[^6bone]を使う荒業も無きにしもあらず。


おすすめ設定には書いてませんが、よく使われるオプションは下記の通り。

```json
{
    "experimental": true,
    "default-address-pools": [
        { "base": "fd00:beaf:cafe::/48", "size": 64 }
    ]
}
```
- `experimental`: 初稿時点ではexperimentalな機能だったのでflagが必要だったが、本稿改訂時点では不要になったみたい。少し前のバージョンでは必要。

- "default-address-pools": docker composeや`docker network create --ipv6 MYBRIDGE
`でbrdigeを作成する際に、割り当てられるIPv6 ネットワークを変更することができる。指定しない場合、自動的にULAが都度生成される。


## 2. `/etc/sysctl.conf`を編集 IPv6 Forwardingを有効化
IPv4はdockerデーモンが勝手に有効化してくれるが、初稿時点(2023)ではIPv6に関しては自前で有効化する必要があった。
改訂時点(2024/11)では勝手にやってくれるようになっているので、新しい環境では気にしなくても良い。

```ini:/etc/sysctl.conf
net.ipv6.conf.all.forwarding = 1
```

`sysctl -p` で有効化

## 3. docker再起動
```bash
systemctl stop docker.socket
systemctl stop docker

systemctl start docker
```

## 4. default bridgeにIPv6アドレスがついていることを確認
```
root@netbox:/opt/netbox-docker# ip addr show docker0 | grep inet6
    inet6 "fd00:ace:cafe:beaf::1/80 scope global tentative
    inet6 fe80::1/64 scope link tentative
```

## 5. 動かしたいアプリのdocker-composeに下記を追加
ここではサービスの例にnginxを挙げている。

```yaml: docker-compose.yml
version: '3'
services:
  nginx:
    restart: unless-stopped
    image: nginx:latest
    ports:
    - '80:80'
    - '443:443'
networks:
  default:
    enable_ipv6: true
```
もし割り当てられるprefixを明示的に指定したい場合、下記のようにする。

```yaml
networks:
  default:
    enable_ipv6: true
    ipam:
      config:
        - subnet: fdca:dead:cafe:ace::/80 # 好きなprefix。最低限/112があれば良いはず。
          gateway: fdca:dead:cafe:ace::1 # ホスト側につくアドレス
```

## 6. 起動して確認
```
docker compose up -d
```

dockerデーモンが勝手に、1)outbound用のNAPTルール作成 2) inbound用のDNATルール作成 までやってくれる。

```bash
# ip6tables-save -t nat
*nat
:PREROUTING ACCEPT [937:73340]
:INPUT ACCEPT [2:128]
:OUTPUT ACCEPT [137:13412]
:POSTROUTING ACCEPT [930:76752]
:DOCKER - [0:0]
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A OUTPUT -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -o br-b6440381d435 -m addrtype --src-type LOCAL -j MASQUERADE
-A POSTROUTING -s fdca:dead:cafe:ace::/64  ! -o br-b6440381d435 -j MASQUERADE
-A POSTROUTING -o docker0 -m addrtype --src-type LOCAL -j MASQUERADE
-A POSTROUTING -s fd00:ace:cafe:beaf::1/80 ! -o docker0 -j MASQUERADE
-A POSTROUTING -s fdca:dead:cafe:ace::3/128 -d fdca:dead:cafe:ace::3/128 -p tcp -m tcp --dport 443 -j MASQUERADE
-A POSTROUTING -s fdca:dead:cafe:ace::3/128 -d fdca:dead:cafe:ace::3/128 -p tcp -m tcp --dport 80 -j MASQUERADE
-A DOCKER -p tcp -m tcp --dport 443 -j DNAT --to-destination [fdca:dead:cafe:ace::3]:443
-A DOCKER -p tcp -m tcp --dport 80 -j DNAT --to-destination [fdca:dead:cafe:ace::3]:80
COMMIT

```


## 参考情報：これまで筆者がやってきた方法たち

### Docker Proxy を利用する方法
https://qiita.com/yas-nyan/items/9dba504af0f735b1c957

- メリット： 楽
- デメリット：
  - コンテナ内からIPv6 Internetへのアクセスが出来ない
  - IPv6はdockerホスト内のproxy経由で、IPv4は通常はNAPT + ポートフォワード、とプロトコルファミリーによってサービス方法が変わるので直感的でない。


### 自作bridgeでNAPT法
1. `docker network create`で自作bridgeを作成。
1. IPv6 NAPT ruleを追加
    a. コンテナ -> Internet用のNAPTルール
    b. Internet -> コンテナ:特定ポートへのDNATのルール
1. netfilter-persistent等で保存 
1. docker-composeで自作bridgeをコンテナにアサイン
1. [netfilter-persistent等](https://qiita.com/yas-nyan/items/e5500cf67236d11cce72)で保存

- メリット： 
  - コンテナ内からもIPv6 Internetへのアクセスが出来る
    - 自作bridgeに割り当てたネットワークプレフィックスによっては、アドレス選択ポリシーの関係でプライベートIPv4アドレスに負ける
      - [きみとぼくとめもと  /etc/gai.conf](https://memo.techack.net/linux/etc/gai.conf.html)
      - [Considerations For Using Unique Local Addresses
](https://datatracker.ietf.org/doc/draft-ietf-v6ops-ula-usage-considerations/)
- デメリット：
  - NAPTルールが再起動後に綺麗に効くようにするために工夫が必要

### その他
NAPTを使わない**IPv6らしい**方法もあるが、可搬性が良くないのであまりオススメできない。

https://valinux.hatenablog.com/entry/20200709


# 参考リンク
https://docs.docker.com/config/daemon/ipv6/

https://github.com/moby/moby/pull/41622

https://forums.unraid.net/topic/105553-allow-enabling-experimental-ip6tables-option-with-docker-2010/

https://forums.docker.com/t/docker-user-chain-for-ip6tables/133961/10