Sådan køres IPv6-aktiverede Docker-containere på AWS

Vil du glemme NAT og køre containere uden at skulle oversætte IP-adresser? Så har du brug for offentlige IP-adresser, masser af dem. Desværre er prisen for hver IPv4-adresse over 20 $, så du får ikke en for hver af dine containere. På den anden side er der ingen mangel på IPv6-adresser, så du i teorien kunne tildele en unik til så mange containere, som du kunne ønske dig.

Da internetprotokollen (IP), der hjælper med at levere dette blogindlæg til din enhed, blev defineret tilbage i 1981, blev internetadresserne, der identificerer kilder og destinationer, specificeret som en fast længde på fire oktetter (32 bit). Dette er faktisk den fjerde version af protokollen, så vi refererer til disse adresser som IP version 4 (IPv4) adresser.

Cirka et årti senere, i 1992, blev det tydeligt, at vi til sidst ville løbe tør for 32-bit IPv4-adresser, så i marts 1994 blev genanvendelige private IP-adresser defineret i et forsøg på at bevare IP-adresserummet. Du bruger disse til at identificere værter, der er private for en virksomhed. Hvis en af ​​disse værter har brug for at oprette forbindelse til en ekstern vært, skal dens adresse oversættes til en - offentlig - IP-adresse, der er globalt unik. Denne proces kaldes NAT Address Network (Network Address Translation) og blev defineret et par måneder senere.

Cirka et år senere (1995) kom en ny version af Internetprotokollen ud for at give blandt andet udvidede adressefunktioner. Vi ved dette som IPv6, hvilket øger IP-adressestørrelsen fra 32 bit til 128 bit.

Problemet? IPv6 er ikke bagud kompatibel med IPv4, derfor har overgangen været virkelig, virkelig langsom ... Over 20 år nu med en nuværende vedtagelse på ~ 22%.

Anyways, formålet med dette indlæg er at demonstrere, hvordan man kører Containere på en Cloud Provider (AWS) ved hjælp af IPv6. Dette var noget, der var verseret fra mit forrige indlæg: Kubernetes multi-klyngenetværk gjorde det enkelt. Måltopologien er følgende.

Selvom vi ikke i øjeblikket kan opdele en IPv6-blok, der er tildelt en VPC (/ 56), til at tildele mindre undernet (/ 64) til forekomster i AWS, kan vi bruge Elastic Network Interfaces (ENI) til at knytte en sammenhængende blok af IPv6-adresser til en instans . Dette genererer en IPv6-præfikslængde større end / 64 — i dette eksempel / 126 - hvilket ikke er den bedste praksis i et LAN, så tag dette med et saltkorn.

Kort sagt, det er hvad vi vil gøre:

  1. Opret EC2-forekomster med en ENI knyttet til den.
  2. Konfigurer IPv6-adressering igen på instansen og installer Docker.
  3. Kør et par containere ved hjælp af kun IPv6.

Opret EC2-forekomster med en ENI knyttet til den

Vi vil bruge AWS CLI-oprette-netværksgrænseflade til at oprette en ENI med en primær IPv6-adresse og også en sammenhængende blok af IPv6-adresser til hvert af vores tilfælde. Disse adresser kommer fra et kendt undernet. Vi anvender også en sikkerhedsgruppe til vores ENI.

Subnet, sikkerhedsgruppe og ENI

Hvis du ikke allerede har en VPC med IPv6-support, skal du tage et kig på Kom godt i gang med IPv6 til Amazon VPC, så du kan gemme ID'et for subnet og sikkerhedsgruppen i variablerne subnetId og sgId.

subnetId = subnet-09a931730fa9exxxx
SGID = sg-0eaf439572982yyyy

For eksempel-1 vil vi reservere adresser :: 1: 1, :: 8, :: 9, :: a og :: b. Jeg har fjernet præfikset for subnet for at lette læsningen. Den første adresse vil være for eksempel, og de andre fire vil gøre den / 126, vi har brug for, til den linux-bro, som containerne vil være forbundet til.

2600: 1f18: 47b: CA03 :: 1: 1
2600: 1f18: 47b: CA03 :: 8
2600: 1f18: 47b: CA03 :: 9
2600: 1f18: 47b: CA03 :: en
2600: 1f18: 47b: CA03 :: b

For vores eksempel-2 reserverer vi adresser :: 2: 2, :: c, :: d, :: e og :: f.

2600: 1f18: 47b: CA03 :: 2: 2
2600: 1f18: 47b: CA03 :: c
2600: 1f18: 47b: CA03 :: d
2600: 1f18: 47b: CA03 :: e
2600: 1f18: 47b: CA03 :: f

Med al denne info udfører vi kommandoen create-network-interface. Vi er dog også nødt til at gemme ID for ENI til følgende operationer, så vi spørger om NetworkInterface.NetworkInterfaceId og gemmer den returnerede værdi i eni1 for eksempel-1.

eni1 = `aws ec2 create-network-interface \
  - subnet-id $ subnetId \
  - beskrivelse "My IPv6 ENI 1" \
  - grupper $ sgId \
  --ipv6-adresser \
  Ipv6Address = 2600: 1f18: 47b: ca03 :: 1: 1 \
  Ipv6Address = 2600: 1f18: 47b: ca03 :: 8 \
  Ipv6Address = 2600: 1f18: 47b: ca03 :: 9 \
  Ipv6Address = 2600: 1f18: 47b: ca03 :: a \
  Ipv6Address = 2600: 1f18: 47b: ca03 :: b \
  - forespørgsel 'NetworkInterface.NetworkInterfaceId' \
  - output-tekst`

Du kan kontrollere den returnerede værdi som følger.

$ ekko $ eni1
eni-08ba7c2f50a22a160

Gentag for den anden ENI.

eni2 = `aws ec2 create-network-interface \
  - subnet-id $ subnetId \
  - beskrivelse "My IPv6 ENI 2" \
  - grupper $ sgId \
  --ipv6-adresser \
  Ipv6Address = 2600: 1f18: 47b: ca03 :: 2: 2 \
  Ipv6Address = 2600: 1f18: 47b: ca03 :: c \
  Ipv6Address = 2600: 1f18: 47b: ca03 :: d \
  Ipv6Address = 2600: 1f18: 47b: ca03 :: e \
  Ipv6Address = 2600: 1f18: 47b: ca03 :: f \
  - forespørgsel 'NetworkInterface.NetworkInterfaceId' \
  - output-tekst`

Lancering af forekomster med ENI vedlagt

Amazon EC2 bruger kryptografi med offentlig nøgle til at kryptere og dekryptere loginoplysninger [Amazon EC2 Key Pairs], så du har brug for en offentlig og privat nøgle for at oprette forbindelse til forekomsterne.

Du kan bruge en eksisterende eller alternativt oprette en som følger, hvor ~ / .ssh / id_rsa.pub er placeringen af ​​din offentlige nøglefil.

aws ec2 import-nøgle-par \
  - nøglenavn  \
  - Offentligt nøglemateriale: // ~/.ssh/id_rsa.pub

Vi lagrer navnet på nøgleparet i en variabel ved navn AWS_SSH_KEY. Du tildeler enten navnet manuelt, som du lige har valgt det, eller henter det fra AWS med beskriv-nøgle-par.

AWS_SSH_KEY = $ (aws ec2 beskrive-nøglepar - forespørgsel KeyPairs [0]. Nøglenavn - output tekst)

Nu er det tid til at oprette forekomster. Vi vil bruge AMI ID ami-0ac019f4fcb7cb7e6, som er Ubuntu Server 18.04 LTS, og forekomst type r5d.large.

Antallet af IP-adresser, du kan tildele til en instans, er begrænset af dens type, så for r5d.large kan vi for eksempel gå op til 10 IPv6-adresser, hvilket er nok til dette lille bevis for koncept. Se detaljerne for for eksempel type i IP-adresser pr. Netværksgrænseflade pr. Instanstype.

Vi vil også vedhæfte den ENI, vi tidligere har oprettet, hvis ID blev gemt i eni1. Vi opbevarer det forekomst-id, vi modtager fra AWS i vm1 (vi spørger om instanser [0] .InstanceId).

vm1 = `aws ec2 run-instances \
  - nøglenavn $ AWS_SSH_KEY \
  --image-id ami-0ac019f4fcb7cb7e6 \
  - instance-type r5d.large \
  - netværk-interfaces DeviceIndex = 0, NetworkInterfaceId = $ eni1 \
  - forespørgsel 'Forekomster [0] .InstanceId' \
  - output-tekst`

Tilsvarende for eksempel-2.

vm2 = `aws ec2 run-instances \
  - nøglenavn $ AWS_SSH_KEY \
  --image-id ami-0ac019f4fcb7cb7e6 \
  - instance-type r5d.large \
  - netværk-interfaces DeviceIndex = 0, NetworkInterfaceId = $ eni2 \
  - forespørgsel 'Forekomster [0] .InstanceId' \
  - output-tekst`

Lad os næste få den første offentlige IPv6-adresse fra instans-1 og gemme den i ip1.

ip1 = `aws ec2 beskriv-instanser \
  - Filternavn = instans-id, værdier = $ vm1 \
  --output tekst \
  - forespørgsel 'Forbehold []. Forekomster []. NetworkInterfaces []. \
Ipv6Addresses [0] .Ipv6Address'`

Du kan nu få adgang til instans-1 med ssh -i ubuntu @ $ {ip1}. Tilsvarende kan du f.eks. Hente den første offentlige IPv6-adresse med:

ip2 = `aws ec2 beskriv-instanser \
  - Filternavn = instans-id, værdier = $ vm2 \
  --output tekst \
  - forespørgsel 'Forbehold []. Forekomster []. NetworkInterfaces []. \
Ipv6Addresses [0] .Ipv6Address'`

Så du kan få adgang til den med ssh -i ubuntu @ $ {ip2}.

Gør forekomsterne til IPv6-venlige

Vi bliver nødt til at installere software i vores tilfælde. Desværre er dette ikke muligt lige fra bat, da vores sources.list-fil kommer med links til us-east-1.ec2.archive.ubuntu.com, der ikke løser en IPv6-adresse. Vi er nødt til at erstatte disse for at bruge archive.ubuntu.com i stedet, som korrekt understøtter IPv6. Du kan gøre dette med sed.

sudo sed -i 's / us-east-1 \ .ec2 \ .// g' /etc/apt/sources.list

Nu kan du bruge apt-get med indstillingen Acquire :: ForceIPv6 = true.

$ sudo apt-get -o Acquire :: ForceIPv6 = sand opdatering
Hent: 1 http://archive.ubuntu.com/ubuntu bionic InRelease [242 kB]
Hent: 2 http://security.ubuntu.com/ubuntu bionic-security InRelease [83,2 kB]
Hent: 3 http://archive.ubuntu.com/ubuntu bionic-updates InRelease [88,7 kB]
...
Hent: 38 http://archive.ubuntu.com/ubuntu bionic-backports / univers Kilder [2068 B]
Hent: 39 http://archive.ubuntu.com/ubuntu bionic-backports / univers amd64-pakker [3468 B]
Hent: 40 http://archive.ubuntu.com/ubuntu bionic-backports / universet Oversættelse-da [1604 B]
Hentet 28,4 MB i 5s (5363 kB / s)
Læse pakkelister ... Udført

Konfigurer IPv6-adressering igen på instansen og installer Docker

Lige nu har vores forekomster en enkelt grænseflade med flere IPv6-adresser. instans-1 viser fem / 128 IPv6-adresser.

$ ip tilføj
...
2: ens5:  mtu 9001 qdisc mq state UP group default qlen 1000
...
    inet6 2600: 1f18: 47b: ca03 :: 1: 1/128 omfang global dynamisk noprefixroute
       valid_lft 385sec foretrukket_lft 85sec
    inet6 2600: 1f18: 47b: ca03 :: 8/128 omfang global dynamisk noprefixroute
       valid_lft 385sec foretrukket_lft 85sec
    inet6 2600: 1f18: 47b: ca03 :: 9/128 omfang global dynamisk noprefixroute
       valid_lft 385sec foretrukket_lft 85sec
    inet6 2600: 1f18: 47b: ca03 :: a / 128 omfang global dynamisk noprefixroute
       valid_lft 385sec foretrukket_lft 85sec
    inet6 2600: 1f18: 47b: ca03 :: b / 128 omfang global dynamisk noprefixroute
       valid_lft 385sec foretrukket_lft 85sec

Ny IPv6-adressefordeling

Vi vil kun have en (/ 64) i hovedgrænsefladen og a / 126 i en linux bridge (docker0) til at allokere adresser til vores containere fra dette interval. Til dette formål redigerer vi netplans konfigurationsfil på /etc/netplan/50-cloud-init.yaml. Det ser oprindeligt sådan ud:

netværk:
  version: 2
  Ethernettet:
    ens5:
      dhcp4: sandt
      dhcp6: sandt
      match:
        makadress: 12: fb: b4: 8b: 15: f8
      sætnavn: ens5

Vi fjerner kun dhcp6-sætningen fra den.

netværk:
  version: 2
  Ethernettet:
    ens5:
      dhcp4: sandt
      match:
        makadress: 12: fb: b4: 8b: 15: f8
      sætnavn: ens5

Som en sidebemærkning og fuldstændigt valgfri kan MAC-adressen til instansen og IPv6-adresserne, der er knyttet til den, hentes fra Instant Metadata når som helst.

$ krølle http://169.254.169.254/latest/meta-data/network/interfaces/macs/
12: fb: b4: 8b: 15: f8

Og:

$ krøll http://169.254.169.254/latest/meta-data/network/interfaces/macs/12:fb:b4:8b:15:f8/ipv6s/
2600: 1f18: 47b: CA03: 0: 0: 0: 8
2600: 1f18: 47b: CA03: 0: 0: 0: 9
2600: 1f18: 47b: CA03: 0: 0: 0: a
2600: 1f18: 47b: CA03: 0: 0: 0: b
2600: 1f18: 47b: CA03: 0: 0: 1: 1
Ja, forekomstmetadata er en IPv4-service, der kun er . Den gode nyhed er, at du ikke har brug for en offentlig IPv4-adresse for at få adgang til den.

Fortsætter med instansens interfacekonfiguration, er vi også nødt til at oprette en separat fil til IPv6-konfigurationen på / etc / netplan / 60-ipv6-static.yaml.

netværk:
  version: 2
  Ethernettet:
    ens5:
      dhcp6: nej
      accept-ra: nej
      adresser:
      - 2600: 1f18: 47b: ca03 :: 1: 1/64
      gateway6: fe80 :: 1066: 30ff: feb8: c008

Vi deaktiverede DHCPv6 (dhcp6: nej) og kasserede IPv6 Router Annoncer (accept-ra: nej). Gatewayinformationen (fe80 :: 1066: 30ff: feb8: c008) kommer fra en iproute2-kommando (ser ud til at den altid er den samme i EC2).

$ ip -6 rute | grep default
standard via fe80 :: 1066: 30ff: feb8: c008 dev ens5 proto ra metrisk 100 præ-medium

Endelig anvender vi vores konfigurationsændringer med netplan gælder.

sudo netplan - debug gælder

Vi gentager for eksempel-2 med de tilsvarende adresser.

Installer Docker

Du kan følge den officielle installationsvejledning eller bare køre følgende kommandoer. Bemærk indstillingen Acquire :: ForceIPv6 = true for apt-get.

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key tilføj -
sudo add-apt-repository "deb [arch = amd64] https://download.docker.com/linux/ubuntu $ (lsb_release -cs) stabil"
sudo apt-get -o Acquire :: ForceIPv6 = sand opdatering
sudo apt-get -o Acquire :: ForceIPv6 = sand installation -y docker-ce
sudo usermod -aG docker $ {USER}

Du er nødt til at logge ud og logge ind, for at brugerændringerne kan træde i kraft.

Vi redigerer / opretter en Docker-konfigurationsfil på /etc/docker/daemon.json for at begynde at allokere IPv6-adresser til vores containere. Skal se sådan ud f.eks. 1.

{
  "ipv6": sandt,
  "fixed-cidr-v6": "2600: 1f18: 47b: ca03 :: 8/126"
}

Genstart derefter dæmonen for at anvende ændringerne; sudo systemctl genstart docker. Vi har nu med succes delt ENI IPv6-adressetildelingen mellem hovedgrænsefladen og Docker-broen.

$ ip tilføj
...
2: ens5:  mtu 9001 qdisc mq state UP group default qlen 1000
...
    inet6 2600: 1f18: 47b: ca03 :: 1: 1/64 omfang globalt
       valid_lft for evigt foretrukket_lft for evigt
...
3: docker0:  mtu 1500 qdisc noqueue state NED gruppe standard
...
    inet6 2600: 1f18: 47b: ca03 :: 9/126 omfang global tentativ
       valid_lft for evigt foretrukket_lft for evigt
...

Gør det samme for eksempel-2 med fast-cidr-v6 = :: c / 126.

Kør et par containere ved hjælp af kun IPv6

Vi er klar til at køre containere. Eller det var i det mindste hvad jeg tænkte. Det viser sig, at registry-1.docker.io og hub.docker.com ikke understøtter IPv6, så vi kan ikke få Docker-billeder fra det.

Kørsel af et billede

Er vi kommet til en blindgyde? Nej, Google Container-registreringsdatabasen redder os! → gcr.io/gcp-runtimes/ubuntu_18_0_4:latest. Lad os køre dette på hver enkelt instans.

docker run -it --rm gcr.io/gcp-runtimes/ubuntu_18_0_4:latest bash

Installer ping og iproute2 i hver container for at udføre nogle forbindelsestests og kontrollere routingtabellen.

apt-get -o Acquire :: ForceIPv6 = sand opdatering
apt-get -o Acquire :: ForceIPv6 = sand installation iputils-ping iproute2 -y

På dette tidspunkt har vi allerede valideret de tilfælde, der kan få adgang til Internettet via IPv6 (via apt-get). Lad os se på de tildelte IP-adresser; vi fik :: a i containeren på instans-1 (container-1). Tilsvarende fik vi :: e i containeren, der kører på instans-2 (container-2).

root @ d7c9480161f9: / # ip tilføj
...
4: eth0 @ if5:  mtu 1500 qdisc noqueue state UP group default
...
    inet6 2600: 1f18: 47b: ca03 :: a / 126 omfang global nodad
       valid_lft for evigt foretrukket_lft for evigt
...

For at gøre mere eksplicit om, at dette fungerer, kan vi pinge en vært på Internettet via IPv6.

root @ d7c9480161f9: / # ping6 ipv6-test.com -c 1
PING ipv6-test.com (agaric.t0x.net (2001: 41d0: 8: e8ad :: 1)) 56 databyte
64 byte fra agaric.t0x.net (2001: 41d0: 8: e8ad :: 1): icmp_seq = 1 ttl = 46 tid = 78,7 ms
--- ipv6-test.com pingstatistik ---
1 pakker sendt, 1 modtaget, 0% pakketab, tid 0ms
rtt min / gennemsnit / max / mdev = 78,788 / 78,788 / 78,788 / 0,000 ms

Ok, lad os nu ping container-2 (d7c9480161f9) fra container-1 (5312fff41595).

root @ d7c9480161f9: / # ping6 2600: 1f18: 47b: ca03 :: e -c 1
PING 2600: 1f18: 47b: ca03 :: e (2600: 1f18: 47b: ca03 :: e) 56 databyte
64 byte fra 2600: 1f18: 47b: ca03 :: e: icmp_seq = 1 ttl = 62 tid = 0.250 ms
--- 2600: 1f18: 47b: ca03 :: e pingstatistik ---
1 pakker sendt, 1 modtaget, 0% pakketab, tid 0ms
rtt min / gennemsnit / max / mdev = 0,250 / 0,250 / 0,250 / 0,000 ms

Den anden vej rundt (container-2 til container-1), bare i tilfælde. Det hele fungerer.

root @ 5312fff41595: / # ping6 2600: 1f18: 47b: ca03 :: a-c 1
PING 2600: 1f18: 47b: ca03 :: a (2600: 1f18: 47b: ca03 :: a) 56 databyte
64 byte fra 2600: 1f18: 47b: ca03 :: a: icmp_seq = 1 ttl = 62 tid = 0.263 ms
--- 2600: 1f18: 47b: ca03 :: en pingstatistik ---
1 pakker sendt, 1 modtaget, 0% pakketab, tid 0ms
rtt min / gennemsnit / max / mdev = 0,263 / 0,263 / 0,263 / 0,000 ms

Hvis dette ikke fungerer for dig, skal du sørge for, at den sikkerhedsgruppe, der anvendes til ENI, tillader IPv6 ICMP fra dine tilfælde. Jeg oprettede specifikt en indgående tilpasset ICMP-regel - IPv6 med den samme sikkerhedsgruppe-ID som kilden for at få dette eksempel til at fungere.

Ruteborde

Lad os udforske rutetabellen i container-1.

root @ d7c9480161f9: / # ip -6 rute
2600: 1f18: 47b: ca03 :: 8/126 dev eth0 proto kernel metrisk 256 pref medium
fe80 :: / 64 dev eth0 proto kernel metrisk 256 pref medium
standard via 2600: 1f18: 47b: ca03 :: 9 dev eth0 metrisk 1024 pref medium

:: 9 er IP'en i docker0 som det ses i en tidligere terminaludgang. Hvad med routingtabel for instans-1?

$ ip -6 rute
2600: 1f18: 47b: ca03 :: 8/126 dev docker0 proto kernel metric 256 pref medium
2600: 1f18: 47b: ca03:: 8/126 dev docker0 metrisk 1024 pref medium
2600: 1f18: 47b: ca03 :: / 64 dev ens5 proto kernel metrisk 256 pref medium
...
standard via fe80 :: 1066: 30ff: feb8: c008 dev ens5 proto statisk metrisk 1024 pref medium

Rådgivning

Docker foreslår, at vi aktiverer IPv6-routing på Linux til at gøre dette arbejde ved at udføre følgende to linjer.

sudo sysctl net.ipv6.conf.default.forwarding = 1
sudo sysctl net.ipv6.conf.all.forwarding = 1

Jeg behøvede ikke at gøre det til dette eksempel, da EC2-tilfælde allerede kom med denne opsætning. De anbefaler heller ikke IPv6-undernet mindre end / 80.

"Subnetet for Docker-containere skal mindst have en størrelse på / 80, så en IPv6-adresse kan slutte med beholderens MAC-adresse, og du forhindrer NDP-nabo-cache-ugyldiggørelsesproblemer i Docker-laget" [Docker]

Sidst, men ikke mindst, løber jeg ind i en diskussion, hvor de siger, at IPv6 er deaktiveret på containere i nogle Docker-versioner. Jeg kører 18.09.0.

$ docker-info -f '{{.ServerVersion}}'
18.09.0

Følgende er netværkskerneindstillingerne for disable_ipv6 i containeren.

root @ d7c9480161f9: / # sysctl -a | grep disable_ipv6
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.eth0.disable_ipv6 = 0
net.ipv6.conf.lo.disable_ipv6 = 0

Konklusion

Selvom dette ikke er nøjagtigt slutmålet, er det interessant at vide, at vi kan køre kun IPv6-containere i skyen i dag.

Dernæst prøver jeg at udvide dette og køre Kubernetes med kun IPv6 på en skyudbyder ... Eller måske tjek først IPv6-support blandt forskellige Cloud-udbydere.