Spis treści
W tym artykule chcę przyjrzeć się chyba najważniejszej z niskopoziomowych serwisów AWS czyli VPC - Virtual Private Cloud, z czego się składa, jak działa i jakie ma komponenty.
W poście będą pojawiać się snippety kodu terraformowego, ich głównym zadainem jest pokazanie zależności między poszczególnymi obiektami. Można je traktować jako przykłady do utworzenia kolejnych zasobów w celach testowych.
Pojawią się również polecenia terminala linuxowego, w tym AWS CLI.
W tekście pojawią się odwołania do innych zasobów AWS, ale nie zamierzam ich omawiać w szczegółach. Pozostawiam to na przyszłe artykuły z serii o AWS.
Krótka przypominka o CIDR
CIDR, czyli Classless Inter-Domain Routing, to metoda przydzielania adresów IP w sieciach bez konieczności stosowania ograniczeń wynikających z podziau klasowego ktory był stosowany wcześniej.
CIDR zapisujemy w formie adresu IP po którym następuje ukośnik i liczba, określająca maskę podsieci (subnet mask) w postaci liczby bitów, używanych do identyfikacji sieci. Liczba po ukośniku może być z zakersu 0-32 dla IPv4, oraz 0-128 dla IPv6.
| CIDR | Maska podsieci | Ilość hostów |
|---|---|---|
| /0 | 0.0.0.0 | 4294967294 |
| /4 | 32.0.0.0 | 268435454 |
| /8 | 255.0.0.0 | 16777214 |
| /16 | 255.255.0.0 | 65534 |
| /24 | 255.255.255.0 | 254 |
| /31 | 255.255.255.254 | 2 |
| /32 | 255.255.255.255 | 1 |
Podstawowe komponenty sieciowe AWS
VPC - Virtual Private Cloud
VPC stanowi absolutną podstawę dla większości innych serwisów AWS, zapewniając logiczną separację między zasobami. Każda instancja EC2 musi być umieszczona w VPC, jak również inne usługi.
Bez VPC nie może istnieć praktycznie nic.
Sam serwis pozwala na zdefiniowanie zakresu adresów IP które będą wykorzystywane
przez zależne od niego zasoby. Zdefiniujmy sobie przykładowy VPC z zakresem
ip /16 co daje adresy z zakresu 10.1.0.0 - 10.1.255.255, czyli 65536 adresów
IPv4 do wykrozystania.
resource "aws_vpc" "exemplar_vpc" {
name = "exemplar_vpc"
cidr = "10.1.0.0/16"
}
Subnety
Wcześniej wspomniana przestrzeń adresów IP zdefiniowana w VPC jest dzielona na mniejsze logiczne bloki, czyli subnety (podsieci). Co bardzo ważne, subnety mogą być publiczne lub prywatne, zależnie od tego czy mają przypisany internet gateway.
Trzeba też pamiętać, że każdy subnet ma przypisany Availability Zone (AZ). Jest to fizyczna lokalizacja centrum danych AWS w ramach jednego regionu. Dzięki istnieniu takigo podziału można zapewnić redundantnośc zasobów a także bardzo potrzebną w systemach rozporoszonych wysoką dostępność - HA (High Availability).
Utwórzmy teraz dwa subnety’: jeden publiczny i jeden prywatny, oba w tym samym AZ.
resource "aws_subnet" "exmplar_subnet_az1_public" {
vpc_id = aws_vpc.exemplar_vpc.id
cidr_block = "10.1.1.0/24"
tags = {
Name = "exemplar_public_subnet_az1"
}
}
resource "aws_subnet" "exemplar_subnet_az1_private" {
vpc_id = aws_vpc.exemplar_vpc.id
cidr_block = "10.1.2.0/24"
tags = {
Name = "exemplar_private_subnet_az1"
}
}
Jeżeli pamiętasz nieco z teorii sieci komputerowych, to wiesz, że dwa adresy IP są zawsze zablokowane - pierwszy jest adresem sieci, a ostatni z zakresu adresem rozgłoszeniowym (broadcast).
W przypadku AWS’a, warto wiedzieć, że zaalokowanych adresów jest pieć zamiast dwóch. Oprócz dwóch wczesniej wymenioinnych, zablokowane są pierwsze 4 oraz ostatni adres z zakresu, czyli dla naszej sieci prywatnej 10.1.2.0/16:
| Adres IP | Opis |
|---|---|
| 10.1.2.0 | Adres sieci |
| 10.1.2.1 | Router VPC |
| 10.1.2.2 | Usługa DNS AWS |
| 10.1.2.3 | Zarezerwowany na przyszłość |
| 10.1.2.255 | Adres rozgłoszeniowy (broadcast) |
Internet Gateway
Internet Gateway, to punkt wejścia dla ruchu sieciowego z i do internetu. Bez niego, zasoby umieszczone w VPC nie będą miały dostępu do internetu.
resource "aws_internet_gateway" "exemplar_igw" {
name = "exemplar_igw"
vpc_id = aws_vpc.exemplar_vpc.id
}
Tablice routingu (RT)
Ostatnim elementem z puli tych najbardziej podstaowwych są tablice routingu, czyli kierukowskazy dla ruchu sieciowego. Bez nich, nie byłoby wiadomo w które miejsce kierować ruch, więc efektywnie - nie byłoby możliwości komunikacji.
Jeżeli chcemy skierować ruch z publicznego subentu do internetu, to należy dodać odpowiednią regułę do tablicy. Tutaj interesuje nas reguła właśnie dla internet gateway:
resource "aws_route_table" "exemplar_rt"
vpc_id = aws_vpc.exemplar_vpc.id
route {
cidr_block = "10.1.1.0/24"
gateway_id = aws_internet_gateway.exemplar_igw.id
}
tags = {
Name = "rt_for_exemplar_vpc"
}
}
Jeżeli podążasz za przykładami które tutaj omówiłem, to w tej chwili posiadasz w pełni funkcjonalny publiczny subnet. Do podsieci prywatnej wrócimy później.
Przejdźmy teraz do najprostszego możliwego przykładu, czyli instancji EC2 dostępnej z publicznej sieci.
Przykład: EC2 w publicznym subnetcie
Jak już wczesniej wspomniałem, żeby instancja miala dostęp do sieci, w najprostszym przypadku wystarczy umieścić ją w publicznym subnecie.
Żeby móc połączyć się z instancją, będziemy potrzebować jeszcze dwóch rzeczy:
- pary kluczy SSH, która pozowli się uwierzytelnić podczas połączenia SSH
- Security group, czyli firewall, z otwartym portem 22 dla ruchu przychodzącego
Utwórzmy zatem te dwa zasoby (tutaj już bez szczegółowego omówienia). Parę kluczy utworzę przez AWS CLI, a SG przez Terraforma:
aws ec2 create-key-pair --key-name exemplar_key_pair > ~/.ssh/aws_exemplar_key_pair.pem
Parę kluczy wystarczy utworzyć raz, a następnie może zostać wykorzystana do autoryzacji na wielu instancjach.
resource "aws_security_group" "exemplar_opened_sg" {
name = "exemplar_opened_sg"
description = "Security group with opened port 22 for ssh access"
vpc_id = aws_vpc.exemplar_vpc.id
}
# otwarcie ruchu wychodzącego bez ograniczeń
resource "aws_vpc_security_group_egress_rule" "allow_all_traffic_ipv4" {
security_group_id = aws_security_group.exemplar_opened_sg.id
cidr_ipv4 = "0.0.0.0/0"
ip_protocol = "-1" # oznacza wszystkie protokoły
}
# otwarcie ruchu przychodzącego na port 22 (ssh)
resource "aws_vpc_security_group_ingress_rule" "allow_ssh" {
security_group_id = aws_security_group.exemplar_opened_sg.id
cidr_ipv4 = "0.0.0.0/0" # otwarcie dla całego internetu
ip_protocol = "tcp"
from_port = 22
to_port = 22
}
Powyższa konfigurację SG stosuję dla uproszczenia, i zdecydowanie nie polceam jej stosowania na dłuższą metę. Obecna tutaj reguła otwiera dostęp do portu 22 dla całego internetu, co jest w praktyce bardzo niebezpiczne. Zdecydowanie lepszym pomysłem byłoby chociażby zweryfikowanie własnego publicznego adresu IP i użycie go w regule.
Swój publiczny IP sprawdzisz np. tutaj: whatismyipaddress.com
Nie biorę odpowiedzialności za ewentualne włamanie na niezabezpieczoną instancję.
Skoro mamy już wszystko przygotowane, to utwórzmy instancję EC2. Identyfikator AMI (czyli obraz systemu) wziąłem z publicznego katalogu AMI.
resource "aws_instance" "exemplar_ec2" {
name = "exemplar_ec2"
# najnowsza wersja amazon linuxa 2023 na moment publikacji posta
ami = "ami-02932c350047fe8b2"
instance_type = "t2.micro"
key_pair = "exemplar_key_pair"
security_groups = [aws_security_group.exemplar_opened_sg.id]
# instancja zostaje utworzona w publicznym subnecie
subnet_id = aws_subnet.exemplar_public_subnet_az1.id
}
Jeżeli spróbujemy teraz połączyć się z tą instancją po ssh, i spróbujemy np. wykonać aktualizację pakietów (zakładam, że jest oparta o Amazon Linuxa)
sudo yum update -y
To zobaczymy, że aktualizacja zostanie rozpoczęta bez żadnego problemu.
Zmieńmy teraz minimalnie konfigurację, prznosząc instancję do prywatnego subnetu:
resource "aws_instance" "exemplar_ec2" {
name = "exemplar_ec2"
ami = "ami-02932c350047fe8b2"
instance_type = "t2.micro"
key_pair = "exemplar_key_pair"
security_groups = [aws_security_group.exemplar_opened_sg.id]
# instancja zostaje utworzona w prywatnym subnecie
subnet_id = aws_subnet.exemplar_private_subnet_az1.id
}
Po zaaplikowaniu tej zmiany, instancja nie będzie miała przydzielonego publicznego adresu IP, a co za tym idzie - na tą chwilę nie będzie miała dostępu do internetu. Już sama próba połączenia się z nią po ssh zakończy się niepowodzeniem.
Póki co - instancja żyje, ale nie da się do niej wejść, wykrozystując klucz SSH.
Żeby teraz sprawdzić co się dzieje, musimy skorzystać z AWS System Manager, który pozwala na dostęp do instancji nawet bez publicznego IP, o ile działa na niej specjalny agent. W amazon linux agent jest zainstalowany domyślnie.
aws ssm start-session --target <identyfikator_instancji>
Kiedy połączymy się z instancją, możemy spróbować wykonać aktualizację pakietów:
sudo yum update -y
Niepowodzenie. Instancja nie ma tutaj dostępu do internetu, więc pobranie aktualizacji nie jest możliwe.
Dostęp do internetu z prywatnych subnetów
Jako osoba zarządzająca chmurą, zdecydowanie nie chciałbym, żeby wszystkie instancje stały w publicznych podsieciach, gdyż jest to proszenie się o kłopoty.
Większośc zasobów powinna być od publicznego dostępu odcięta, przy zachowaniu możliwości inicjowania połączeń wychodzących do internetu, np. w celu pobrania aktualizacji.
NAT Gateway
Standardowym rozwiązaniem tego problemu jest użycie NAT Gateway, czyli usługi pozwalającej na inicjowanie połączeń wychodzących z prywatnych subnetów do internetu, ale jednocześnie nie pozwalającej na przyjmowanie połączeń przychodzących z internetu.
Przykładowy Gateway można utworzyć w następujący sposób:
resource "aws_nat_gateway" "exemplar_nat_gw" {
name = "exemplar_nat_gw"
allocation_id = aws_eip.exemplar_eip.id
subnet_id = aws_subnet.exemplar_private_subnet_az1.id
}
Istotnym elementem jest tutaj elastic IP, który jest niezbędny do działania tej usługi, jak punkt dostępu dla ruchu przychodzącego z internetu.
Elastic IP
Elastic IP to statyczny, publiczny adres IPv4, który można przypisać zarówno do NAT Gateway, jak również do instnacji EC2. Pozwala to na zachowanie niezmiennego adresu, nawet jeżeli zasób, do którego jest przypisany, zostanie zatrzymany i ponownie uruchomiony.
Tworząc elastic IP, nie możemy zdecydować jaki dokładnie adres otrzymamy. AWS przyddziela go z dostępnej puli.
resource "aws_eip" "exemplar_eip" {
name = "exemplar_eip"
tags = {
Name = "exemplar_eip_for_nat_gateway"
}
}
Ciekawostka: Egress-Only Internet Gateway (EOIG)
Zasadniczo, EOIG jest podobny do NAT Gateway w tym znaczeniu, że również pozwala na incjowanie połączeń z prywatnej sieci jednocześnie nie pozwalając na ich przyjmowanie z internetu.
Różnica jest taka, że EOI jest przenzaczony tylko do działania w protokole IPv6, podczas gdy NAT Gateway działa tylko w IPv4.
Istotnym plusem EOIG jest jego cena - jest darmowy. W przypadku NAT Gateway musimy płacić za samą usługę, jak również ze wykorzystanie wykorzystanie elastic IP, który jest niezbędny do działania tej usługi.
resource "aws_egress_only_internet_gateway" "exemplar_eoig" {
vpc_id = aws_vpc.exemplar_vpc.id
tags = {
Name = "exemplar_eoig"
}
}
Jeżeli chcemy korzystać z EOIG, to należy pamiętać o zdefinowaniu zakresu adresów IPv6 w VPC, a także o przypisaniu go do subnetów, które mają korzystać z tej usługi.
Podaję to jako ciekawostkę, gdyż konfiguracja IPv6 jest nieco bardziej złożona, a nie chciałbym dodatkowo komplikować tego artykułu.
Przykład: EC2 w prywatnym subnetcie z dostępem do internetu
Ponieważ poprzednio umieściliśmy już instancję w prywatnym subnecie, to dodajmy teraz teraz właśnie NAT Gateway, żeby zapewnić jej dostęp do internetu.
Najpierw należy zaalokować elastic IP:
resource "aws_eip" "exemplar_eip" {
name = "exemplar_eip"
tags = {
Name = "exemplar_eip_for_nat_gateway"
}
}
W następnym kroku dodajemy NAT Gateway, który będzie korzystał z tego elastic IP:
resource "aws_nat_gateway" "exemplar_nat_gw" {
name = "exemplar_nat_gw"
allocation_id = aws_eip.exemplar_eip.id
subnet_id = aws_subnet.exemplar_public_subnet_az1.id
tags = {
Name = "exemplar_nat_gw"
}
}
I ostatni krok, to aktualizacja tablicy routing do prywatnego subnetu, by ruch wychodzący był kierowany do NAT Gateway:
resource "aws_route_table" "exemplar_rt"
vpc_id = aws_vpc.exemplar_vpc.id
route {
cidr_block = "10.1.1.0/24"
gateway_id = aws_internet_gateway.exemplar_igw.id
}
route {
cidr_block = "10.1.2.0/24"
nat_gateway_id = aws_nat_gateway.exemplar_nat_gw.id
}
tags = {
Name = "rt_for_exemplar_vpc"
}
}
Po zaaplikowaniu zmian, instancja EC2 w prywatnym subnecie będzie miała dostęp do internetu, co pozwoli na wykonanie na niej aktualizacji. Jedyny problem jest taki, że w dalszym ciągu nie będziemy w stanie zalogować się do niej przez SSH, gdyż nie ma przypisanego publicznego adresu IP.
Wykorzystując jednak AWS System Manager, możemy połączyć się z instancją i wykonać aktualizację pakietów:
local~$: aws ssm start-session --target <id_instancji>
remote~$: sudo yum update -y
Tym razem - aktualizacje zostaną pobrane i zainstalowane bez problemu. Dokładnie tak jak miało być.
Schemat zależności
Na zakończenie
W kolejnym wpisie z tej serii, przyjrzymy się w jaki sposób można nieco ograniczyć lub wręcz zepsuć komunikację między zasobami w VPC, czyli omówimy Security Groups i NACL’s (Network Access Control Lists), jak komunikkować się między kilkoma VPC, a także w jaki sposób działające na instancjach aplikacje komunikują się z samym AWS’em
I jeszcze jedno - jeżeli podążałeś za przykładami, to koniecznie pamiętaj o usunięciu zasobów, żeby nie generowały kosztów. W szczególności dotyczy to NAT Gateway, EC2 oraz Elastic IP, które są płatne.