Créer des instances AWS qui ont accès à Internet sans IP publique avec Terraform

Il nous est souvent demandé : Comment est-ce que je fais pour créer des instances AWS qui ont accès à internet mais sans IP publique ?

Tout d’abord, qu’est ce qu’une IP publique? Une IP publique est une adresse IP joignable sur internet. A contrario des adresses IP privées (décrites dans la RFC 1918) qui elles ne sont pas visibles de l’extérieur du réseau.

L’architecture Amazon à mettre en place

Schéma de l’architecture à mettre en place

Tout d’abord nous avons besoin d’un VPC (Virtual Private Cloud) (1) qui va héberger nos différents sous-réseaux (subnets). Un VPC est une brique AWS nous permettant ici de gérer les éléments réseaux de notre infrastructure.

Pour avoir un accès à internet, il faut poser une Internet Gateway (2). L’Internet Gateway est le composant AWS permettant de lier les éléments d’un VPC à Internet.

Ensuite nous avons un Subnet “publique”  (3) : toutes les instances / objets à l’intérieur seront routées sur internet.

Nous allons ensuite mettre en place deux Subnets dit “privés” (4) : les instances / objets à l’intérieur ne seront pas directement routées sur internet, ils auront accès à internet sans être accessible depuis l’extérieur.

Enfin, pour permettre à des instances d’accèder à internet sans avoir d’IP publiques, nous allons utiliser une NAT Gateway (http://docs.aws.amazon.com/fr_fr/AmazonVPC/latest/UserGuide/vpc-nat-gateway.html).

Le concept “Network Address Translation” (NAT), permet de transposer des adresses réseaux, souvent privées, en d’autres adresses. C’est un concept régulièrement utilisé pour des composants d’un réseau privé pour avoir accès à internet sans être accessible (adressable) depuis l'extérieur (c’est le fonctionnement des box que nous avons tous chez nous).

La NAT Gateway se pose dans le subnet publique et dispose d’une adresse publique, en l'occurrence un Elastic IP (EIP) chez AWS. Il reste à s’intéresser au routage de la NAT Gateway aux instances des subnets privés.

Le S_ubnet_ publique aura sa propre RouteTable (routeur chez AWS). Celle-ci aura une route qui fera sortir tout le traffic sortant (0.0.0.0/0) vers l’Internet Gateway.

Les S_ubnets_ privés auront une autre RouteTable, celle-ci aura aussi une route qui permettra de faire sortir le traffic sortant (0.0.0.0/0) mais cette fois vers la NAT Gateway et non vers l’Internet Gateway.

Pour implémenter cela, nous allons utiliser Terraform qui va déployer l’architecture sur AWS. Pour en savoir plus sur Terraform et son fonctionnement : https://blog.octo.com/deployer-son-infrastructure-google-cloud-platform-grace-a-terraform.

Déploiement de notre infrastructure

Commençons par exporter les variables d’environnements nécessaire (credentials et région) :

export AWS_ACCESS_KEY_ID=<XXXXXXXXXXXXXX> export AWS_SECRET_ACCESS_KEY=<XXXXXXXXXXXXXX> export AWS_DEFAULT_REGION=<XXXX>

Nous avons besoin de récupérer la région courante, pour cela nous allons utiliser un Datasource Terraform (https://www.terraform.io/docs/providers/aws/d/region.html) :

data "aws_region" "region" {  current = true }

Créons notre VPC :

resource "aws_vpc" "my_vpc" {  cidr_block = "10.0.0.0/16"

tags {    Name = "my_vpc"  } }

Ensuite nous allons créer tous les composants nécessaire au S_ubnet_ publique :

resource "aws_subnet" "public_subnet" {  vpc_id = "${aws_vpc.my_vpc.id}****"  cidr_block = "10.0.0.0/24"  availability_zone = **"****${data.aws_region.current.name}**a"

map_public_ip_on_launch = true   tags {    Name = "public subnet"  } }

Dans la configuration du subnet, nous pouvons voir que pour le champs availability_zone nous utilisons le datasource de la region.

Nous spécifions aussi le champs map_public_ip_on_launch à true. Cela permet à toutes les instances lancées dans ce subnet d’avoir par défaut une IP publique_._

Pour le déploiement de notre NAT Gateway nous avons besoin de deux objets : l’IP publique et la NAT Gateway en elle-même :

resource "aws_eip" "nat_eip" {  vpc = true }

resource "aws_nat_gateway" "nat" {  allocation_id = "${aws_eip.nat_eip.id}"  subnet_id     = "${aws_subnet.public_subnet.id}" }

Nous posons donc la NAT Gateway dans le subnet publique. Petit détail, une NAT Gateway quand on la pose dans le Subnet dispose aussi d’une IP privée. Celle-ci est très rarement utilisée, nous verrons juste après dans la RouteTable que nous préférons utiliser l’ID de la NAT Gateway.

Sur cette première partie, il nous reste à mettre en place le routage :

resource "aws_route_table" "public_routetable" {  vpc_id = "${aws_vpc.vpc.id}"  tags {    Name = "Public Routetable"  } }

resource "aws_route" "public_route" {  route_table_id         = "${aws_route_table.public_routetable.id}"  destination_cidr_block = "0.0.0.0/0"  gateway_id             = "${aws_internet_gateway.internet_gateway.id}" }

resource "aws_route_table_association" "public_subnet_a" {  subnet_id      = "${aws_subnet.public_subnet.id}"  route_table_id = "${aws_route_table.public_routetable.id}" }

Passons maintenant à la deuxième partie, la création des subnets privés. Nous allons créer deux subnets, un dans l’Availability Zone A et un autre dans l’Availability Zone B.

resource "aws_subnet" "private_subnet_a" {  vpc_id = "${aws_vpc.my_vpc.id}****"  cidr_block = "10.0.1.0/24"  availability_zone = **"****${data.aws_region.current.name}**a"   tags {    Name = "private subnet A"  } }

resource "aws_subnet" "private_subnet_b" {  vpc_id = "${aws_vpc.my_vpc.id}****"  cidr_block = "10.0.2.0/24"  availability_zone = **"****${data.aws_region.current.name}**b"   tags {    Name = "private subnet B"  } }

Nous allons passer à la dernière partie : la configuration du routage de la partie privée. Il faut donc créer la RouteTable, les association et bien entendu la route qui dirige vers la NAT Gateway.

resource "aws_route_table" "private_routetable" {  vpc_id = "${aws_vpc.vpc.id}"  tags {    Name = "private Routetable"  } }

resource "aws_route_table_association" "private_subnet_a" {  subnet_id      = "${aws_subnet.private_subnet_a.id}"  route_table_id = "${aws_route_table.private_routetable.id}" }

resource "aws_route_table_association" "private_subnet_b" {  subnet_id      = "${aws_subnet.private_subnet_b.id}"  route_table_id = "${aws_route_table.private_routetable.id}" }

resource "aws_route" "nat_route" {  route_table_id         = "${aws_route_table.private_routetable.id}"  destination_cidr_block = "0.0.0.0/0"  gateway_id             = "${aws_nat_gateway.nat.id}" }

Nous allons créer deux outputs Terraform pour permettre d’indiquer en sortie les subnets dans lesquels il faut poser les instances.

output "private_subnet_a_id" {  value         = "${aws_subnet.private_subnet_a.id}" }

output "private_subnet_b_id" {  value         = "${aws_subnet.private_subnet_b.id}" }

Le code est disponible ici : https://github.com/mathieuherbert/aws-terraform/blob/master/vpc_with_nat_gateway

Conclusion

Nous avons vu que permettre à des instances d’avoir à accès à internet sans être accessible demande plusieurs objets. Ces objets peuvent être compliqués à manipuler dans un premier temps. Il existe une implémentation plus historique et qui n’est plus conseillée par AWS : la NAT Instance. Celle-ci demande de manager soi-même son instance et sa disponibilité (http://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_NAT_Instance.html ).

Concernant la disponibilité de la NAT Gateway, celle-ci est dépendante de la disponibilité de la zone de son Subnet d'hébergement. Pour assurer la disponibilité sur une région au globale, une NAT Gateway par zone de disponibilité est nécessaire avec des impacts sur le routage.