Como Criar um NAT Instance na AWS

SimplesCloudSimplesCloud
15 min read

Introdução

Você pode ter diversas razões e motivos para querer configurar uma instância NAT na AWS, para meu caso de uso estava precisando criar um novo ambiente de QA, este ambiente consistia em ter uma função Lambda que fosse capaz de ter uma conexão com a internet e também com recursos da VPC.

Felizmente uma função Lambda pode ser configurada dentro de uma VPC e então conceder acesso aos recursos dela, mas infelizmente ao fazer isso, essa mesma função que agora pode se comunicar com RDS, Elasticache e outros recursos da VPC, perde a conexão com a internet, isso ocorre porque a AWS irá atribuir um ENI com endereço IP privado na função lambda quando adicionada na VPC, desta forma impedindo sua conexão com a internet.

Há uma solução sugerida pela AWS para este caso, que consiste em adicionar um NAT Gateway, isso é robusto, escalável e com certeza um caminho a seguir para um ambiente de produção. O problema que me encontrei aqui é que o NAT Gateway é um serviço caro, gerenciado pela AWS e que funciona o tempo todo, então parecia um grande exagero para um ambiente de QA que funciona em alguns horários pré-definidos.

Como citei, esse era o meu cenário, mas novamente você pode querer ter um NAT por qualquer outro motivo, por exemplo para fornecer conexões a internet de qualquer recurso criado em uma sub-rede privada, como instâncias, RDS, etc.

Como alternativa, optei por não usar o serviço gerenciado NAT Gateway, mas sim criei e configurei minha própria instância NAT, compartilharei com você uma breve descrição do que significa o NAT e passaremos por um tutorial de como atingi esse objetivo.


O que é NAT?

NAT significa Network Address Translation, é uma técnica utilizada em redes de computadores para traduzir endereços IP de um formato para outro. Seu principal objetivo é permitir que vários dispositivos em uma rede compartilhem um único endereço IP externo, isso é especialmente útil em redes domésticas e empresariais, onde há escassez de endereços IP públicos disponíveis.

Por exemplo, quando você contrata um plano de internet para sua casa, sua operadora não entregará para você um endereço IP público para cada um de seus dispositivos, como laptops, computadores, TVs, impressoras, celulares, etc. Mas sim entregará um IP público dinâmico e todos os seus equipamentos conectados em seu roteador poderão usar o mesmo IP público dinâmico fornecido através do NAT, pense nisso conforme diagrama abaixo:

Onde cada endereço IP 192.168.0.X de seus equipamentos conectados é entregue através do DHCP configurado em seu roteador, e todos obtém um único endereço IP público, esse funcionamento se dá através do NAT configurado em seu roteador.

No contexto da AWS, o NAT desempenha um papel parecido e é fundamental na comunicação entre instâncias em redes privadas e recursos externos, como a internet. Ao criar instâncias em uma sub-rede privada, ela não terá um endereço IP público, mas sim um endereço privado da sua VPC, o que as impede de se comunicar diretamente com a internet. Então é neste momento que o NAT trabalha, pois atua por meio de tradução de endereços IP, conforme diagrama abaixo:

Cada recurso criado na sub-rede privada, possui uma rota para um NAT que está em uma sub-rede pública com acesso à internet, permitindo então assim que os recursos privados consigam acesso a internet.

Concluído essa introdução, espero que você esteja um pouco mais familiarizado com esse conceito, partiremos agora para o tutorial prático.


Tutorial

Antigamente, a AWS fornecia uma AMI pronta para instâncias NAT, porém seu suporte foi descontinuado em dezembro de 2023, conforme anúncio oficial:

A AMI NAT é baseado na última versão do Amazon Linux AMI, 2018.03, que atingiu o fim do suporte padrão em 31 de dezembro de 2020 e o fim do suporte de manutenção em 31 de dezembro de 2023.

Como alternativa, a AWS sugere criar sua própria instância NAT baseado no Amazon Linux mais recente, porém eu não gostaria de usar essa imagem e sim me basear no Ubuntu, bom e foi isso que eu fiz, abaixo estão os passos:


Pré-Requisitos

Este artigo é extenso, passaremos por muitos passos, para evitar que fique ainda maior, não cobriremos a configuração de uma VPC do zero, também não discutiremos em detalhes sobre a VPC ou rede, se você já possui uma VPC com sub-redes públicas e privadas, pode pular algumas etapas e passos que descrevo abaixo, senão for o caso, passaremos pelas principais configurações necessárias.


Criando Grupo de Segurança

1- Acesse o console AWS → VPC → Security Group → Create Security Group e defina de acordo com suas preferências:

  • Name: por exemplo nat-instance-sg

  • Description: por exemplo Security group with permissions required for NAT Instance

  • Inbound: suas regras de entrada, por exemplo HTTP, HTTPS e SSH

💡
Dica: em SSH estou definindo um Management Prefix List, você pode querer consultar esse tutorial para saber mais:

👉 Como melhorar a administração e organização dos Grupos de Segurança da AWS com o VPC Prefix List

  • Outbound: suas regras de saída, você precisará adicionar HTTP e HTTPS para 0.0.0.0/0 para ter conexão com a internet

  • Tags: se preferir, adicione tags


Criando Nat Instance

1- Acesse o console AWS → EC2 → Instance → Launch Instance → preencha as informações:

  • Name: defina um nome de sua preferência, por exemplo NAT Instance

  • AMI: escolha Ubuntu

  • Architecture: escolha 64-bit (x86)

  • Instance Type: escolha o tamanho, lembre-se que todas as conexões irão passar por essa instância, então definir uma instância muito pequena pode aumentar o gargalo e a latência da conexão com a internet, porém para meu caso de uso que era um ambiente de QA, o tipo micro pareceu ser uma boa escolha.

  • Keypair: escolha seu keypair, isso é sua chave de acesso que precisaremos dele depois para conseguir conectar através de SSH

  • VPC: escolha o ID da sua VPC

  • Subnet: defino como sem preferências, pois não me importa muito em qual sub-rede nossa instância NAT será criada, porém se preferir você pode definir uma subnet pública e não privada

    O NAT Instance deve ser criada em uma sub-rede Pública!
  • Auto-assign Public IP: escolho habilitado, pois desejo que essa instância NAT tenha um endereço IP público criado automaticamente

  • Security Group: escolho o grupo de segurança que criamos anteriormente

  • Storage: defino um tamanho e seleciono o tipo como gp3

  • Por fim clico em Launch Instance


Configurando o NAT Instance

1- Com nossa instância criada, acesso através de SSH → e primeiro atualizo os pacotes do Ubuntu com o comando:

sudo apt update -y

2- Agora verifico qual o nome da interface de rede primária com o comando:

ip a

E descubro que ela se chama ens5

Isso pode mudar dependendo o tipo de instância e a AMI que você selecionou, então se o nome para você for diferente, apenas anote pois o usaremos em breve.

3- Agora ativo o encaminhamento de IP com os comandos:

echo "net.ipv4.ip_forward = 1" | sudo tee -a /etc/sysctl.conf

sudo sysctl -p

4- Então agora criamos a regra para o NAT com o comando:

Lembre-se de alterar ens5 para o nome correto da sua interface de rede que descobrimos anteriormente se for o caso.
# Making a catchall rule for routing and masking the private IP
sudo iptables -t nat -A POSTROUTING -o ens5 -s 0.0.0.0/0 -j MASQUERADE

5- Agora precisamos tornar essa regra persistente, pois quando acontecer um reboot ou stop nessa instância, a regra do iptables para o NAT será perdida e você certamente não gostará de ter que acessar ela toda vez para repetir este comando, então para isso criamos uma pasta chamada iptables no diretório etc:

sudo mkdir -p /etc/iptables

6- E salvo nossa regra em um arquivo chamado rules.v4 com o comando:

sudo iptables-save | sudo tee /etc/iptables/rules.v4

7- Neste momento, nossa regra já está salva, mas ainda assim não está automático, seria necessário ainda acessar a instância para restaurar nossa regra, bom vamos corrigir isso agora instalando o iptables-persistent no modo silencioso que fará esse trabalho automático para nós.

sudo debconf-set-selections <<EOF
iptables-persistent iptables-persistent/autosave_v4 boolean true
iptables-persistent iptables-persistent/autosave_v6 boolean true
EOF

sudo apt-get -y install iptables-persistent

Pronto! O NAT Instance já foi configurado e as configurações estão persistentes para que ela sempre funcione, mesmo se reiniciar ou desligar sem qualquer interação.

Desativando Verificações de Origem/Destino

1- Ainda não acabamos tudo, precisamos agora parar a verificação de origem e destino dessa instância, então acesso o console AWS → EC2 → seleciono a instância → Actions → Networking → Change source/destination check

2- Clico em Stop


Criando Tabela de Roteamento na VPC

1- Acesse o console AWS → VPC → Route Tables → Create Route Table

2- Defina um nome de sua preferência como por exemplo Private Route → selecione sua VPC → defina Tags se desejar → clique em Create Route Table

3- Agora clique em Edit Routes

4- Adicione uma rota:

  • Destination:0.0.0.0/0

  • Target:escolha o ID da sua instância NAT

Você então verá que a interface de rede dessa instância foi adicionado corretamente para 0.0.0.0/0


Criando Sub-Redes Privadas

Como estou na região de Ohio, tenho três Availabilty Zones disponíveis:

  • us-east-2a

  • us-east-2b

  • us-east-2c

Já tenho 3 sub-redes públicas criadas por padrão, então criarei outras três sub-redes privadas para cada Availability Zone .


1- Acesse o console da AWS → VPC → Subnets → Create Subnet

💡
Dica: defina um nome fácil para identificação, se olharmos a imagem acima veremos que o último bloco CIDR é 172.31.32.0/20 para o Availabilty Zone us-east-2c. Para você não precisar calcular esses blocos CIDR, clique na seta para direita (>), isso irá preencher para você o próximo bloco CIDR corretamente.
  • us-east-2a | 172.31.48.0/20

  • us-east-2b | 172.31.64.0/20

  • us-east-2c | 172.31.80.0/20

2- Volte para o menu Route Tables → selecione a Tabela de Roteamento Privado que criamos → selecione a aba Subnet Associations → Edit

3- Selecione agora as sub-redes privadas criadas → clique em Save Association


Testando

Fizemos muitas coisas até aqui, por favor não se sinta "perdido", recapitulando o que fizemos até aqui:

  • Criamos o Grupo de Segurança

  • Criamos o NAT Instance na sub-rede pública

  • Desativamos Verificações de Origem/Destino

  • Criamos a Tabela Privada de Roteamento

  • Criamos as Sub-redes Privadas

Neste momento, já temos tudo devidamente configurado, mas e agora como testar se tudo está funcionando? Bom para isso vou fazer 2 testes:

  • Criar uma função Lambda dentro da VPC (lembre-se que isso perde a conexão dela com a internet) e testar sua conexão com a internet através do NAT Instance

  • Criar uma instância EC2 na sub-rede privada e testar sua conexão com a internet


Lambda

Para adicionar a função Lambda que vamos criar, precisamos antes criar uma policy e role , vamos fazer isso agora:

Policy

1- Acesse o console AWS → IAM → Policies → Create Policy → crie a política com o código abaixo:

Lembre-se de alterar <your-region>, <your-account-id> e <your-lambda-function-name> para seus valores reais
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ec2:DescribeNetworkInterfaces",
                "ec2:CreateNetworkInterface",
                "ec2:DeleteNetworkInterface",
                "ec2:DescribeInstances",
                "ec2:AttachNetworkInterface"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": "logs:CreateLogGroup",
            "Resource": "arn:aws:logs:<your-region>:<your-account-id>:*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": [
                "arn:aws:logs:<your-region>:<your-account-id>:log-group:/aws/lambda/<your-lambda-function-name>:*"
            ]
        }
    ]
}

2- Defina de acordo com suas preferências:

  • Name: por exemplo lambda-test-internet-connection-policy

  • Description: por exemplo Policy with necessary permissions to be used in the lambda function named test-internet-connection


Role

1- Acesse o console AWS → IAM → Role → Create Role → crie a role escolhendo:

  • AWS Services

  • Lambda

2- Adicione a política que acabamos de criar

3- Defina de acordo com sua preferência:

  • Name: por exemplo lambda-test-internet-connection-role

  • Description: por exemplo Role with required permissions used in lambda function named test-internet-connection


Criando o Lambda Function

1- Acesse o console AWS → Lambda → Create Function → Author from Scratch

2- Defina de acordo com sua preferência:

  • Name: por exemplo test-lambda-internet-connection

3- Escolha as opções:

  • Runtime:Node.js

  • Architecture:x86_64

  • Use an existing role: escolha a role que criamos anteriormente

4- A função será criada → clique na aba Code → renomeie o arquivo de index.mjs para index.js (remova o m do nome)

5- Apague o código criado automaticamente pela AWS → adicione este código:

const getContent = function (url) {
  // return new pending promise
  return new Promise((resolve, reject) => {
    // select http or https module, depending on reqested url
    const lib = url.startsWith('https') ? require('https') : require('http');
    const request = lib.get(url, (response) => {
      // handle http errors
      if (response.statusCode < 200 || response.statusCode > 299) {
        reject(new Error('Failed to load page, status code: ' + response.statusCode));
      }
      // temporary data holder
      const body = [];
      // on every content chunk, push it to the data array
      response.on('data', (chunk) => body.push(chunk));
      // we are done, resolve promise with those joined chunks
      response.on('end', () => resolve(body.join('')));
    });
    // handle connection errors of the request
    request.on('error', (err) => reject(err))
  })
};

exports.handler = async (event) => {
  console.log('Received event: %j', event);

  const url = event.url || 'https://www.google.com';
  try {
    const content = await getContent(url);
    console.log('Response content: %j', content);
    console.log('You have access to the internet, congratulations!');

    return {
      statusCode: 200,
      body: JSON.stringify({
        message: 'You have access!',
        content,
      }),
    };
  } catch (e) {
    console.error(e);
    return {
      statusCode: 500,
      body: JSON.stringify({
        message: e.toString(),
      }),
    };
  }
}

6- Clique em Configure Test Event

7- Defina um nome de sua preferência, por exemplo TestInternetConnection

8- Clique em Deploy → Test

Você receberá uma resposta que funcionou, isso aconteceu porque nossa função lambda ainda não está dentro do VPC, então ela por padrão tem conexão com a internet.

9- Vamos colocar nossa função lambda na VPC → clique na aba Configuration → VPC → Edit

10- Selecione as Sub-redes privadas → e o grupo de segurança (escolhi o default-vpc security group)

11- Clico em Test novamente e concluo:

A função lambda sem estar em uma VPC, por padrão possui conexão com a internet, mas não possui conexão com recursos da VPC como RDS, instâncias, Elasticache, etc.
A função lambda adicionada na VPC em sub-redes públicas, pode se conectar com recursos da VPC como RDS, instâncias, Elasticache, etc. Mas não pode se conectar na internet, pois o lambda irá atribuir um endereço privado a um ENI em sua VPC, no qual não terá roteamento para a internet, necessitando de uma rota para um NAT.
A função lambda adicionada na VPC em sub-redes privadas, pode se conectar com recursos da VPC como RDS, instâncias, Elasticache, etc. E também poderá se conectar na internet através de um NAT, pois o lambda irá atribuir um endereço privado a um ENI em sua VPC, e essa terá um roteamento para a internet.


Criando um EC2 Privado

1- Acesse o console AWS → EC2 → Launch Instance → defina de acordo com sua preferência:

  • Name: por exemplo ec2-private-test-internet-connection

  • AMI:Amazon Linux

Escolhi a AMI Amazon Linux por já ter o SSM Manager Agent instalado por padrão para conseguirmos fazer ssh através do Session Manager, uma vez que esta instância de teste será criada na sub-rede privada e não terá um IP público.

  • Escolha uma sub-rede privada

  • Verifique se a opção Auto-assign public IP é marcada como Disable


Role

1- Com nossa instância criada, precisaremos criar uma role para permitir conexões através do Session Manager, então vou para o console AWS → IAM → AWS Service → seleciono EC2

2- Adiciono a permissão AmazonSSMManagedInstanceCore

3- Defina um nome e descrição de acordo com suas preferências:

  • Name: por exemplo ec2-session-manager-role

  • Description: por exemplo Role with required permissions to allow access to EC2 through Session Manager

4- Volto para o console AWS → EC2 → seleciono a instância privada de teste → Actions → Security → Modify IAM Role

5- Seleciono a role que criamos anteriormente

6- Estamos quase lá, agora edito as regras de entrada do grupo de segurança do NAT Instance para permitir o ping dos blocos CIDR das sub-redes privadas

Estou usando o mesmo grupo de segurança para o NAT Instance e para o teste, se você criar grupos de segurança separados, lembre-se de ajustar suas regras de entrada e saída seguindo a mesma lógica e raciocínio aqui.

7- Edito agora nas regras de saída para permitir que o NAT Instance envie o ping para a internet

8- Agora com tudo pronto, vamos tentar conectar em nossa instância privada que não tem um IP público, através do Session Manager que possui permissões adequadas no IAM Role que foi anexada em nossa instância. Para isso seleciono novamente a instância privada de teste → Connect

9- Altero para a aba Session Manager → Connect

10- Executo um ping para o Google:

ping google.com

Pronto! Nossa instância privada que não tem um IP público, também possui conexão com a internet através do nosso NAT Instance.

Conclusão

Fizemos uma breve explicação sobre o NAT Instance, também observamos o porque ele seria útil para meu caso de uso, além disso passamos por um tutorial de como configuramos um NAT Instance no Ubuntu com configurações persistentes para que funcione mesmo depois de reiniciar ou desligar.

Garantimos que mesmo após de um reboot, as regras do NAT estarão funcionando:

Garantimos também que mesmo que a instância esteja parada (stopped), quando ligarmos ela, automaticamente a ENI da tabela de rota privada que criamos mudará o status para ativo sem qualquer ação manual, fazendo com que o NAT funcione.

Também criamos dois testes práticos e reais:

  • uma função lambda na VPC com conexão na internet

  • e outra instância EC2 privada com conexão na internet, para isso configuramos também permissões adequadas.

Ao final fomos capazes de atingir o objetivo.


Como mencionei no início, este meu cenário eu não precisava da instância ligada o tempo, então se você também estiver interessado em reduzir ainda mais seu custo, talvez ache interessante agendar o STAR/STOP de uma instância EC2, veja mais detalhes sobre como fazer isso neste outro artigo:

Com isso consegui reduzir um custo inteiro mensal de um NAT Gateway para o preço sob demanda do EC2 em uma instância mínima e com horários agendados de funcionamento.


Lembre-se de excluir todos os recursos ao final para não obter cobranças indesejadas e manter sua conta AWS limpa e organizada.
Espero que as informações tenham sido úteis!
0
Subscribe to my newsletter

Read articles from SimplesCloud directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

SimplesCloud
SimplesCloud