在2016年的reInvent,AWS正式推出了IPv6的支持,但是直到2021年reInvent前,整整5年的时间,AWS都一直在打造一个IPv4/v6双栈的网络环境,例如ALB/NLB/S3的双栈支持等。就笔者自2018年起在美国的经验来看,移动运营商的IPv6支持已经相当成熟了。
也正是随着IPv6的大规模应用,AWS虽迟但到,在2021年reInvent前夕(11月23日),先是上线了IPv6 Only子网,允许你创建一个只有IPv6的子网(并在其中启动EC2),同日也上线了ALB和NLB的IPv6目标组支持。紧接着次日就上线了NAT64和DNS64,这两个服务是非常重要的,它使得IPv6 Only网络可以通过NAT网关访问IPv4 Only的服务。IPv6 Only上线不是毫无征兆的,时间拉回到2021年8月25日,Instance Metadata Service,Amazon Time Sync Service以及Amazon VPC DNS Server这几个服务的IPv6端点上线,这其实就是为了EC2达成IPv6 Only扫清最后一波的障碍。本文会按照以下的拓扑创建一个简单的VPC环境,这包含一个开启了IPv6的VPC,一个双栈的公有子网,一个IPv6仅出口私有子网,同时会在公有子网中创建一个NAT网关用于DNS64服务。

图片

本文所有资源都在ap-east-1 (HKG)区域创建。

一、创建VPC、子网、互联网网关(IGW)、仅出口网关(EIGW)、NAT网关以及路由表
1. 创建VPC

aws ec2 create-vpc --cidr-block 10.0.0.0/16 --amazon-provided-ipv6-cidr-block

记录返回的id,返回结果中表明IPv6地址块正在分配,这时我们可以describe一下

aws ec2 describe-vpcs --vpc-id vpc-0228200a8c51f2f75

便可以拿到我们已分配的IPv6地址块,"2406:da1e:55c:ee00::/56"
2. 创建子网    

2.1 创建双栈的子网

aws ec2 create-subnet --vpc-id vpc-0228200a8c51f2f75 --cidr-block 10.0.0.0/24 --ipv6-cidr-block 2406:da1e:55c:ee00::/64

2.2 创建IPv6 Only子网

aws ec2 create-subnet --vpc-id vpc-0228200a8c51f2f75 --ipv6-cidr-block 2406:da1e:55c:ee01::/64 --ipv6-native

我们可以看出来这两者的区别,如果不含ipv6-native参数,则必须包含—cidr-block来指定IPv6地址块。
3. 配置双栈子网为公有子网    3.1 创建互联网网关,这一步是为了将双栈子网配置为公有子网的前置工作

aws ec2 create-internet-gateway

3.2 将VPC与网关关联,这一步不会有返回

aws ec2 attach-internet-gateway --vpc-id vpc-0228200a8c51f2f75 --internet-gateway-id igw-0830a75ac7836b885

3.3 进一步创建公有子网的路由表,注意记录路由表的id

aws ec2 create-route-table --vpc-id vpc-0228200a8c51f2f75

3.4 由于这个公有子网是双栈的,所以我们需要在路由表中创建两条路由

aws ec2 create-route --route-table-id rtb-03e17b7efdde4b8c2 --destination-cidr-block 0.0.0.0/0 --gateway-id igw-0830a75ac7836b885
aws ec2 create-route --route-table-id rtb-03e17b7efdde4b8c2 --destination-ipv6-cidr-block ::/0 --gateway-id igw-0830a75ac7836b885

3.5 我们将双栈子网与该路由表关联,这样该子网便配置为公有子网了。

aws ec2 associate-route-table --subnet-id subnet-0c778dcea7d948e01 --route-table-id rtb-03e17b7efdde4b8c2

4. 创建NAT网关,以用于之后NAT64的配置。    

4.1 首先需要创建一个IPv4 EIP

aws ec2 allocate-address

4.2 然后创建NAT网关

aws ec2 create-nat-gateway --allocation-id eipalloc-04558a754d4a94b77 --subnet-id subnet-0c778dcea7d948e01

4.3 NAT网关的创建需要一定的时间,我们可以用describe来确认

aws ec2 describe-nat-gateways --nat-gateway-ids nat-07acd181fb9959117

5 配置IPv6 Only子网为仅出口私有子网    

5.1 创建仅出口网关

aws ec2 create-egress-only-internet-gateway --vpc-id vpc-0228200a8c51f2f75

5.2 创建路由表(但是目前我们还不能创建IPv6 Only的路由表)

aws ec2 create-route-table --vpc-id vpc-0228200a8c51f2f75

5.3 在路由表中创建路由,将IPv6流量路由到仅出口网关

aws ec2 create-route --route-table-id rtb-01c06b0d23482d25c --destination-ipv6-cidr-block ::/0 --egress-only-internet-gateway-id eigw-00f2192f26d173465

5.4 最后将IPv6 Only子网和该路由表关联

aws ec2 associate-route-table --subnet-id subnet-01f2091f8d394166c --route-table-id rtb-01c06b0d23482d25c

6. 添加NAT64路由

该IPv6 Only子网就成功配置了IPv6仅出口私有子网,为了之后使用NAT64访问IPv4 Only资源,我们需要进一步添加路由到该路由表中。

aws ec2 create-route --route-table-id rtb-01c06b0d23482d25c --destination-ipv6-cidr-block 64:ff9b::/96 --nat-gateway-id nat-07acd181fb9959117

7. 对公有子网进行配置接下来我们对子网做配置,对于公有子网,我们打开自动分配IPv6、公有IPv4地址,并且打开资源名称

aws ec2 modify-subnet-attribute --subnet-id subnet-0c778dcea7d948e01 --assign-ipv6-address-on-creation
aws ec2 modify-subnet-attribute --subnet-id subnet-0c778dcea7d948e01 --map-public-ip-on-launch
aws ec2 modify-subnet-attribute --subnet-id subnet-0c778dcea7d948e01 --private-dns-hostname-type-on-launch resource-name
aws ec2 modify-subnet-attribute --subnet-id subnet-0c778dcea7d948e01 --enable-resource-name-dns-a-record-on-launch
aws ec2 modify-subnet-attribute --subnet-id subnet-0c778dcea7d948e01 --enable-resource-name-dns-aaaa-record-on-launch

8. 对于IPv6仅出口私有子网进行配置

我们打开DNS64

aws ec2 modify-subnet-attribute --subnet-id subnet-01f2091f8d394166c --enable-dns64

至此网络部分就配置完成了。
二、创建实例并测试

下面这个部分,我们将在公有子网和IPv6仅出口私有子网中,启动两个实例。

1. 公有子网实例(双栈)
1.1 创建密钥对

aws ec2 create-key-pair --key-name IPv6Demo --query "KeyMaterial" --output text > IPv6Demo.pem

1.2 创建安全组

aws ec2 create-security-group --group-name SSHAccess --description "Security group for SSH access" --vpc-id vpc-0228200a8c51f2f75

1.3 为它添加规则

aws ec2 authorize-security-group-ingress --group-id sg-03f4bc74bd9c97f62 --protocol tcp --port 22 --cidr 0.0.0.0/0

1.4 创建实例

aws ec2 run-instances --image-id ami-0f6b3e4242c6690f2 --count 1 --instance-type t4g.small --key-name IPv6Demo --security-group-ids sg-03f4bc74bd9c97f62 --subnet-id subnet-0c778dcea7d948e01

实例创建需要时间,稍等片刻,我们describe一下,拿到它的IPv4地址(以及IPv6地址)

aws ec2 describe-instances --instance-id i-0850f347a6c453e11

1.5 尝试登录

ssh ec2-user@18.xxx.42.221 -i IPv6Demo.pem

我们测试一下,

[ec2-user@i-0850f347a6c453e11 ~]$ ping -n ietf.org
PING ietf.org (50.223.129.194) 56(84) bytes of data.
64 bytes from 50.223.129.194: icmp_seq=1 ttl=38 time=157 ms
64 bytes from 50.223.129.194: icmp_seq=2 ttl=38 time=157 ms
64 bytes from 50.223.129.194: icmp_seq=3 ttl=38 time=157 ms
64 bytes from 50.223.129.194: icmp_seq=4 ttl=38 time=157 ms

可以看到正常返回这时我们尝试一下ping6

[ec2-user@i-0850f347a6c453e11 ~]$ ping6 -n ietf.org
PING ietf.org(2001:559:c4c7::100) 56 data bytes
64 bytes from 2001:559:c4c7::100: icmp_seq=1 ttl=43 time=160 ms
64 bytes from 2001:559:c4c7::100: icmp_seq=2 ttl=43 time=160 ms
64 bytes from 2001:559:c4c7::100: icmp_seq=3 ttl=43 time=160 ms
64 bytes from 2001:559:c4c7::100: icmp_seq=4 ttl=43 time=160 ms

可见ping6也是正常的

2. IPv6仅出口私有子网实例 (IPv6 Only)

2.1 创建安全组

aws ec2 create-security-group --group-name SSHAccessRestricted --description "Security group for SSH access from bastion" --vpc-id vpc-0228200a8c51f2f75

2.2 添加规则,为了简单起见,直接放行来自Bastion的所有流量

aws ec2 authorize-security-group-ingress --group-id sg-00b2064115792ce7e --protocol all --source-group sg-03f4bc74bd9c97f62

2.3 创建EC2

aws ec2 run-instances --image-id ami-0f6b3e4242c6690f2 --count 1 --instance-type t4g.small --key-name IPv6Demo --security-group-ids sg-00b2064115792ce7e --subnet-id subnet-01f2091f8d394166c

同样可以稍等片刻后,使用describe拿到IPv6地址

aws ec2 describe-instances --instance-id i-012b271b4229728a8

这时我们可以尝试先登录跳板机,然后用跳板机登录这台实例

ssh-add IPv6Demo.pem
ssh -A ec2-user@18.xxx.42.221
ssh ec2-user@2406:da1e:55c:ee01:c8f7:3b17:6323:1a16

可以正常登录了

[ec2-user@i-0850f347a6c453e11 ~]$ ssh ec2-user@2406:da1e:55c:ee01:c8f7:3b17:6323:1a16
Last login: Thu Sep 15 15:43:01 2022 from 2406:da1e:55c:ee00:aa20:xxxx:7174:8028
       __|  __|_  )
       _|  (     /   Amazon Linux 2 AMI
      ___|\___|___|
https://aws.amazon.com/amazon-linux-2/
3 package(s) needed for security, out of 10 available
Run "sudo yum update" to apply all updates.
[ec2-user@i-012b271b4229728a8 ~]$

这时我们首先确认IPv6互联网是通的

[ec2-user@i-012b271b4229728a8 ~]$ ping6 -n ietf.org
PING ietf.org(2001:559:c4c7::100) 56 data bytes
64 bytes from 2001:559:c4c7::100: icmp_seq=1 ttl=43 time=161 ms
64 bytes from 2001:559:c4c7::100: icmp_seq=2 ttl=43 time=161 ms
64 bytes from 2001:559:c4c7::100: icmp_seq=3 ttl=43 time=161 ms
64 bytes from 2001:559:c4c7::100: icmp_seq=4 ttl=43 time=161 ms

3. 测试由于目前金数据是IPv4 Only,正好可以用来模拟仅IPv6的EC2通过NAT64访问IPv4 Only互联网资源的情况。

3.1 我们确认解析

[ec2-user@i-012b271b4229728a8 ~]$ dig jinshuju.net AAAA
; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.amzn2.5.2 <<>> jinshuju.net AAAA
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 15433
;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;jinshuju.net.                  IN      AAAA
;; ANSWER SECTION:
jinshuju.net.           300     IN      AAAA    64:ff9b::3453:27fe
jinshuju.net.           300     IN      AAAA    64:ff9b::3453:b9a5
jinshuju.net.           300     IN      AAAA    64:ff9b::3453:c172
;; Query time: 150 msec
;; SERVER: fd00:ec2::253#53(fd00:ec2::253)
;; WHEN: Thu Sep 15 15:53:12 UTC 2022
;; MSG SIZE  rcvd: 125

可以看到,DNS64将无AAAA地址的A地址,转换为64:ff9b::/96的AAAA地址返回给客户端,这时我们curl一下

[ec2-user@i-012b271b4229728a8 ~]$ curl https://jinshuju.net/health-check -I
HTTP/2 200
date: Thu, 15 Sep 2022 15:54:48 GMT

可以看到正常返回。

3.2 Amazon Time Sync Service

[ec2-user@i-012b271b4229728a8 ~]$ chronyc sources -v
  .-- Source mode  '^' = server, '=' = peer, '#' = local clock.
 / .- Source state '*' = current best, '+' = combined, '-' = not combined,
| /             'x' = may be in error, '~' = too variable, '?' = unusable.
||                                                 .- xxxx [ yyyy ] +/- zzzz
||      Reachability register (octal) -.           |  xxxx = adjusted offset,
||      Log2(Polling interval) --.      |          |  yyyy = measured offset,
||                                \     |          |  zzzz = estimated error.
||                                 |    |           \
MS Name/IP address         Stratum Poll Reach LastRx Last sample
===============================================================================
^* 169.254.169.123               3   4   377     9  +4315ns[+5194ns] +/-  379us
^- time.cloudflare.com           3   8   377     7  +3983us[+3983us] +/-   75ms
^- the.lonely.email              2   8   377     6   -100us[ -100us] +/-   29ms
^- time.cloudflare.com           3   8   377     6  +4079us[+4079us] +/-   76ms
^- ntp.xtom.com.hk               3   8   377     0    -90us[  -90us] +/- 3906us

比较奇怪的是只有IPv4,遂打开/etc/chrony.conf,确认默认情况下该配置默认就是IPv4,更改为IPv6端点

server fd00:ec2::123 prefer iburst minpoll 4 maxpoll 4

重启chronyd后得到想要的结果

[ec2-user@i-012b271b4229728a8 ~]$ sudo systemctl restart chronyd
[ec2-user@i-012b271b4229728a8 ~]$ chronyc sources -v
  .-- Source mode  '^' = server, '=' = peer, '#' = local clock.
 / .- Source state '*' = current best, '+' = combined, '-' = not combined,
| /             'x' = may be in error, '~' = too variable, '?' = unusable.
||                                                 .- xxxx [ yyyy ] +/- zzzz
||      Reachability register (octal) -.           |  xxxx = adjusted offset,
||      Log2(Polling interval) --.      |          |  yyyy = measured offset,
||                                \     |          |  zzzz = estimated error.
||                                 |    |           \
MS Name/IP address         Stratum Poll Reach LastRx Last sample
===============================================================================
^? fd00:ec2::123                 3   4     3     2    -11us[  -11us] +/-  432us
^? 64:ff9b::dfff:b902            1   6     7     0   +257us[ +257us] +/- 2289us
^? ntp.xtom.com.hk               3   6     3     2    +94us[  +94us] +/- 2866us
^? tanss.it-risch.de             2   6     3     2    +14ms[  +14ms] +/-  179ms
^? coffre.bitschine.fr           2   6     3     2    -20ms[  -20ms] +/-  128ms

3.3 Metadata service

[ec2-user@i-012b271b4229728a8 ~]$ curl http://169.254.169.254/latest/meta-data/ami-id
ami-0f6b3e4242c6690f2
[ec2-user@i-012b271b4229728a8 ~]$ curl http://[fd00:ec2::254]/latest/meta-data/ami-id
curl: (28) Failed to connect to fd00:ec2::254 port 80 after 130547 ms: Connection timed out

居然超时了,确认这里还需要进一步打开IPv6

aws ec2 modify-instance-metadata-options --instance-id i-012b271b4229728a8 --http-protocol-ipv6 enabled
[ec2-user@i-012b271b4229728a8 ~]$ curl http://[fd00:ec2::254]/latest/meta-data/ami-id
ami-0f6b3e4242c6690f2

正常了。

3.4 Amazon VPC DNS Service

[ec2-user@i-012b271b4229728a8 ~]$ cat /etc/resolv.conf
options timeout:2 attempts:5
; generated by /usr/sbin/dhclient-script
nameserver fd00:ec2::253
nameserver 10.0.0.2


注意事项

1. IPv6 Only的实例,究竟是不是没有IPv4?答案是有IPv4,如下

[ec2-user@i-012b271b4229728a8 ~]$ ifconfig
eth0: flags=4163  mtu 9001
        inet 169.254.229.223  netmask 255.255.255.255  broadcast 0.0.0.0
        inet6 fe80::405:44ff:fe0c:414c  prefixlen 64  scopeid 0x20
        inet6 2406:da1e:55c:ee01:c8f7:3b17:6323:1a16  prefixlen 128  scopeid 0x0
        ether 06:05:44:0c:41:4c  txqueuelen 1000  (Ethernet)
        RX packets 50762  bytes 67759873 (64.6 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 5627  bytes 1098359 (1.0 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
lo: flags=73  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 96  bytes 7520 (7.3 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 96  bytes 7520 (7.3 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

这也就是为什么NTP和Metadata Service可以正常工作的原因。

2. NAT64既可以使IPv6 Only的对象访问互联网资源,也可以访问VPC内的其他内网资源,比如原IPv4的RDS。
3. 补充资料,里面有更多场景的IPv6架构https://docs.aws.amazon.com/whitepapers/latest/ipv6-on-aws/IPv6-on-AWS.html
总结

AWS通过多年的布局,终于在2021 reInvent完善了对IPv6 Only的支持,同时我们也看到今年多个更新都为各个服务增加了IPv6支持。另外以上所有的步骤,均可以在亚马逊云科技中国区域实现。