Однажды команда, которая хочет иметь выделенный кластер Kubernetes для своих услуг. Услуги, которые будут развернуты, должны быть выявлены через шлюз API и защищены через MTL.
Цель этой истории является основной, есть несколько способов ее достижения, и мы увидим один способ сделать это. Не идеальное, а решение, способ развернуть кластер, управляемый кластер, с доступными и защищенными контейнерами/услугами.
Вы готовы?
Как и другие реальные проекты, наш проект имеют ограничения.
Нам нужно развернуть кластер Kubernetes, это факт. Но где? Можем ли мы справиться с нашим правильным кластером Kubernetes? У нас есть специальная команда? Нет … поэтому нам нужно выбрать управляемый кластер Kubernetes. Можем ли мы выбрать нашего облачного провайдера? Нет … ограничение — установить его в AWS … Итак, давайте начнем с EKS, Kubernetes управлял кластером от AWS!:-)
Чтобы развернуть кластер AWS EKS, существует несколько решений, вы можете загрузить и использовать командную строку EKSCTL или использовать Terraform.
Наша команда всегда управляет своей инфаструктурой, управляемыми ресурсами с Terraform, инфраструктурой в качестве инструмента кода (IAC).
Существующий модуль Terraform существует, чтобы развернуть AWS EKS, но:
- Это старый модуль, а не подъем и не поддерживается
- это не совместимо с TF 0,12
Таким образом, мы определим наши надлежащие ресурсы.
Чтобы получить доступ к услугам, развернутым в контейнерах, существует несколько решений. Нам нужен обратный проток, развертываемый в качестве входа в Kubernetes, легко развертываем и совместимый с MTL, мы выберем Traefik.
Мы не хотим создавать новый DN вручную, поэтому мы будем использовать внешние DNS, связанные с нашими услугами, которые создадут записи AWS Route53.
Для MTLS мы создадим наш собственный PKI и генерируем наши собственные сертификаты CA Root, сервера и клиента.
А для тестов мы создадим коллекцию тестов и используем почтальон.
Мы знаем, что хотим развернуть с Terraform A AWS EKS Cluster. Так конкретно нам нужно развернуть много ресурсов для этого кластера EKS, много ресурсов … 😅
Здесь список ресурсов AWS для определения:
- Эк
- IAM Роли
- IAM Роли Политики
- IAM POLICIES привязанность
- IAM OpenID Connect Provider
- Асг
- IG
- Подготовительный
- Подсети
- Маршрут 53
Прежде всего, нам нужно инициализировать нашу кодовую организацию.
Предварительные результаты: установите Terraform CLI на вашем машине.
Создает папку Terraform:
$ cd my_git_repository $ mkdir terraform $ cd terraform/
Теперь мы, как обычно, создаем Backend.tf :
В этом файле мы определяем хранилище S3. Хорошей практикой является хранение вашего государства Terraform удаленно. Таким образом, мы будем хранить государство в ведре AWS S3:
# Backend configuration is loaded early so we can't use variables terraform { required_version = ">= 0.12" backend "s3" { region = "eu-central-1" bucket = "my-tf-state-bucket" key = "eks-cluster.tfstate" encrypt = true } }
Далее нам нужно определить Provider.tf Файл, в котором мы определим поставщика AWS, который мы используем, и в конечном итоге роль IAM, которую мы должны взять на себя:
############### PROVIDERS DEFINITION ############### provider "aws" { region = var.aws_region }
Итак, теперь мы можем определить наш кластер EKS и группу узлов кластера EKS в eks.tf файл:
resource "aws_eks_cluster" "eks-scraly-cluster" { name = local.cluster_name role_arn = aws_iam_role.eks-scraly-cluster-ServiceRole.arn vpc_config { security_group_ids = [ aws_security_group.eks-scraly-cluster-sg.id ] subnet_ids = concat(aws_subnet.eks-subnet-private[*].id, aws_subnet.eks-subnet-public[*].id) endpoint_private_access = true public_access_cidrs = var.eks_public_access_cidrs } # Ensure that IAM Role permissions are created before and deleted after EKS Cluster handling. # Otherwise, EKS will not be able to properly delete EKS managed EC2 infrastructure such as Security Groups. depends_on = [ aws_iam_role_policy_attachment.eks-scraly-cluster-AmazonEKSClusterPolicy, aws_iam_role_policy_attachment.eks-scraly-cluster-AmazonEKSServicePolicy, ] version = var.eks_version tags = local.tags } resource "aws_eks_node_group" "eks-scraly-cluster-node-group" { cluster_name = aws_eks_cluster.eks-scraly-cluster.name node_group_name = "eks-scraly-cluster-node-group" node_role_arn = aws_iam_role.eks-scraly-cluster-worker-role.arn subnet_ids = aws_subnet.eks-subnet-private[*].id scaling_config { desired_size = 1 max_size = 1 min_size = 1 } # Ensure that IAM Role permissions are created before and deleted after EKS Node Group handling. # Otherwise, EKS will not be able to properly delete EC2 Instances and Elastic Network Interfaces. depends_on = [ aws_iam_role_policy_attachment.eks-scraly-cluster-AmazonEKSWorkerNodePolicy, aws_iam_role_policy_attachment.eks-scraly-cluster-AmazonEKS_CNI_Policy, aws_iam_role_policy_attachment.eks-scraly-cluster-AmazonEC2ContainerRegistryReadOnly, ]}
EKS нуждается в большом количестве ролей, политики и политики, давайте создадим их в iam.tf файл:
resource "aws_iam_role" "eks-scraly-cluster-ServiceRole" { name = "${var.eks_resource_prefix}-ServiceRole" path = "/" assume_role_policy = jsonencode({ Version = "2012-10-17" Statement = [{ Action = "sts:AssumeRole" Effect ="Allow" Principal = { Service = [ "eks.amazonaws.com", "eks-fargate-pods.amazonaws.com" ] } }] }) tags = local.tags } resource "aws_iam_role_policy_attachment" "eks-scraly-cluster-AmazonEKSClusterPolicy" { policy_arn = "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy" role = aws_iam_role.eks-scraly-cluster-ServiceRole.name } resource "aws_iam_role_policy_attachment" "eks-scraly-cluster-AmazonEKSServicePolicy" { policy_arn = "arn:aws:iam::aws:policy/AmazonEKSServicePolicy" role = aws_iam_role.eks-scraly-cluster-ServiceRole.name } resource "aws_iam_role_policy_attachment" "eks-scraly-cluster-NLBPolicy" { policy_arn = aws_iam_policy.nlb_iam_policy.arn role = aws_iam_role.eks-scraly-cluster-ServiceRole.name } resource "aws_iam_role_policy_attachment" "eks-scraly-cluster-ExternalDnsRoute53" { policy_arn = aws_iam_policy.externalDNS_iam_policy.arn role = aws_iam_role.eks-scraly-cluster-ServiceRole.name } resource "aws_iam_role" "eks-scraly-cluster-worker-role" { name = "${var.eks_resource_prefix}-worker-role" assume_role_policy = jsonencode({ Statement = [{ Action = "sts:AssumeRole" Effect = "Allow" Principal = { Service = "ec2.amazonaws.com", } }] Version = "2012-10-17" }) tags = local.tags } resource "aws_iam_role_policy_attachment" "eks-scraly-cluster-AmazonEKSWorkerNodePolicy" { policy_arn = "arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy" role = aws_iam_role.eks-scraly-cluster-worker-role.name } resource "aws_iam_role_policy_attachment" "eks-scraly-cluster-AmazonEKS_CNI_Policy" { policy_arn = "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy" role = aws_iam_role.eks-scraly-cluster-worker-role.name } resource "aws_iam_role_policy_attachment" "eks-scraly-cluster-AmazonEC2ContainerRegistryReadOnly" { policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly" role = aws_iam_role.eks-scraly-cluster-worker-role.name } resource "aws_iam_role_policy_attachment" "eks-scraly-cluster-CertManagerRoute53" { policy_arn = aws_iam_policy.certmanager_route53_iam_policy.arn role = aws_iam_role.eks-scraly-cluster-worker-role.name } ### External cli kubergrunt data "external" "thumb" { program = [ "get_thumbprint.sh", var.aws_region ] } # Enabling IAM Roles for Service Accounts resource "aws_iam_openid_connect_provider" "eks-scraly-cluster-oidc" { client_id_list = ["sts.amazonaws.com"] thumbprint_list = [data.external.thumb.result.thumbprint] url = aws_eks_cluster.eks-scraly-cluster.identity.0.oidc.0.issuer } data "aws_iam_policy_document" "eks-scraly-cluster-assume-role-policy" { statement { actions = ["sts:AssumeRoleWithWebIdentity"] effect = "Allow" condition { test = "StringEquals" variable = "${replace(aws_iam_openid_connect_provider.eks-scraly-cluster-oidc.url, "https://", "")}:aud" values = ["sts.amazonaws.com"] } principals { identifiers = ["${aws_iam_openid_connect_provider.eks-scraly-cluster-oidc.arn}"] type = "Federated" } } } resource "aws_iam_role" "eks-scraly-cluster-externalDns-role" { description = "Role used by External-DNS to manage route 53 inside the cluster" assume_role_policy = data.aws_iam_policy_document.eks-scraly-cluster-assume-role-policy.json name = "${var.eks_resource_prefix}-ServiceRole-ExternalDns" tags = local.tags } resource "aws_iam_role_policy_attachment" "eks-scraly-cluster-externalDNS-attachment" { policy_arn = aws_iam_policy.externalDNS_iam_policy.arn role = aws_iam_role.eks-scraly-cluster-externalDns-role.name } resource "aws_iam_policy" "certmanager_route53_iam_policy" { name = "${var.eks_resource_prefix}-CertManagerRoute53Policy" path = "/" description = "Route53 policy IAM Policy for eks" policy = jsonencode( { Version: "2012-10-17" Statement: [ { Effect: "Allow" Action: "route53:GetChange" Resource: "arn:aws:route53:::change/*" }, { Effect: "Allow" Action: "route53:ChangeResourceRecordSets" Resource: "arn:aws:route53:::hostedzone/*" }, { Effect: "Allow" Action: "route53:ListHostedZonesByName" Resource: "*" } ] } ) } resource "aws_iam_policy" "externalDNS_iam_policy" { name = "${var.eks_resource_prefix}-externalDNSPolicy" path = "/" description = "ExternalDNS IAM Policy for eks" policy = jsonencode( { Version: "2012-10-17" Statement: [ { Effect: "Allow" Action: [ "route53:ChangeResourceRecordSets" ] Resource: [ "arn:aws:route53:::hostedzone/*" ] }, { Effect: "Allow", Action: [ "route53:ListHostedZones", "route53:ListResourceRecordSets" ], Resource: [ "*" ] } ] } ) } resource "aws_iam_policy" "nlb_iam_policy" { name = "${var.eks_resource_prefix}-NLBPolicy" path = "/" description = "NLB IAM Policy for eks" policy = jsonencode( { Version: "2012-10-17" Statement: [ { Action: [ "elasticloadbalancing:*", "ec2:CreateSecurityGroup", "ec2:Describe*" ], Resource: "*", Effect: "Allow" } ] } ) }
Нам нужно развернуть кластер EKS в сетевых ресурсах, поэтому давайте создадим их в Network.tf файл:
data "aws_vpc" "eks-vpc" { filter { name = "tag:Name" values = [ var.eks_vpc_name ] } } data "aws_nat_gateway" "nat-gateway" { count = length(var.nat_gateway_ids) id = var.nat_gateway_ids[count.index] } data "aws_route_table" "eks-route-table-public" { # There is a single route table for all public subnet filter { name = "tag:Name" values = [ var.route_table_name_public ] } } data "aws_route_table" "eks-route-table-private" { count = length(var.route_table_name_private) filter { name = "tag:Name" values = [ var.route_table_name_private[count.index] ] } } data "aws_internet_gateway" "internet-gateway" { filter { name = "tag:Name" values = [ var.internet_gateway_name ] } } resource "aws_route_table_association" "rt-association-private" { count = length(aws_subnet.eks-subnet-private) subnet_id = aws_subnet.eks-subnet-private[count.index].id route_table_id = data.aws_route_table.eks-route-table-private[count.index].id } resource "aws_route_table_association" "rt-association-public" { count = length(aws_subnet.eks-subnet-public) subnet_id = aws_subnet.eks-subnet-public[count.index].id route_table_id = data.aws_route_table.eks-route-table-public.id } resource "aws_security_group" "eks-scraly-cluster-sg" { name = "eks-scraly-cluster-sg" description = "Cluster communication with worker nodes" vpc_id = data.aws_vpc.eks-vpc.id egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } tags = local.tags } ################ # Public subnet ################ resource "aws_subnet" "eks-subnet-public" { count = length(var.subnet_public_cidr) vpc_id = data.aws_vpc.eks-vpc.id cidr_block = var.subnet_public_cidr[count.index] availability_zone = length(regexall("^[a-z]{2}-", element(local.azs, count.index))) > 0 ? element(local.azs, count.index) : null availability_zone_id = length(regexall("^[a-z]{2}-", element(local.azs, count.index))) == 0 ? element(local.azs, count.index) : null map_public_ip_on_launch = true tags = merge( { "Name" = format( "%s-public-%s", var.eks_subnet_prefix, element(local.azs, count.index), ), "kubernetes.io/cluster/${local.cluster_name}" = "shared" }, local.tags, local.public_subnet_tags ) } ################# # Private subnet ################# resource "aws_subnet" "eks-subnet-private" { count = length(var.subnet_public_cidr) vpc_id = data.aws_vpc.eks-vpc.id cidr_block = var.subnet_private_cidr[count.index] availability_zone = length(regexall("^[a-z]{2}-", element(local.azs, count.index))) > 0 ? element(local.azs, count.index) : null availability_zone_id = length(regexall("^[a-z]{2}-", element(local.azs, count.index))) == 0 ? element(local.azs, count.index) : null map_public_ip_on_launch = false tags = merge( { "Name" = format( "%s-private-%s", var.eks_subnet_prefix, element(local.azs, count.index), ), "kubernetes.io/cluster/${local.cluster_name}" = "shared" }, local.tags, local.private_subnet_tags ) }
Теперь мы создадим Местные жители.tf Файл, в котором мы создадим уловки для создания будущего файла KubeConfig. Да, файл, который нам нужен, чтобы получить доступ к нашему будущему кластеру Kubernetes:-).
# Get the Availability Zones to automatically set default subnets number (1 in each AZ) data "aws_availability_zones" "az_list" {} locals { azs = data.aws_availability_zones.az_list.names tags = { Application = "${var.tag_application}" Contact = "${var.tag_contact}" Tool = « Terrafor »m } cluster_name = var.eks_cluster_name public_subnet_tags = { Description = "Public subnet for ${var.tag_application} environment" Usage = "Public" Type = "Public" "kubernetes.io/role/elb" = "1" } private_subnet_tags = { Description = "Private subnet for ${var.tag_application} environment" Usage = "Private" Type = "Private" } kubeconfig = <
Теперь нам нужно вывести некоторую полезную информацию (например, содержимое файла KubeConfig), поэтому давайте определим их в outputs.tf файл:
output "endpoint" { value = aws_eks_cluster.eks-scraly-cluster.endpoint } output "kubeconfig" { value = "${local.kubeconfig}" } output "arn_external_dns_role" { value = aws_iam_role.eks-scraly-cluster-externalDns-role.arn }
Если мы хотим создать новый маршрут Route53, вы можете создать Route53.tf Файл, но лично я буду использовать существующий. Кластер будет доступен через *.scraly.com
Маршрут 53.
Мы используем некоторые входные переменные в наших ресурсах, поэтому давайте cretae переменные.
variable "region" { default = "eu-central-1" } variable "vpc_cidr" { description = "CIDR of the VPC to create. Example : 10.1.0.0/22" default = "10.xx.x.x/16" } variable "subnet_public_cidr" { description = "For public ips" type = list(string) default = ["10.xx.x.x/24","10.xx.x.x/24","10.xx.x.x/24"] } variable "subnet_private_cidr" { description = "For private ips" type = list(string) default = ["10.xx.x.x/24","10.xx.x.x/24","10.xx.x.x/24"] } variable "eks_vpc_id" { default = "vpc-123456" } variable "eks_vpc_name" { default = "our_existing_vpc" } variable "eks_subnet_prefix" { default = "eks-scraly" } variable "nat_gateway_ids" { description = "The NAT gateway to be used by the EKS worker to reach the internet" default = [ "nat-lalala", "nat-scraly" ] } variable "internet_gateway_name" { default = "ig_scraly } variable "route_table_name_public" { default = "scraly } variable "route_table_name_private" { default = [ "scraly-private-eu-central-1a", "scraly-private-eu-central-1b", "scraly-private-eu-central-1c" ] } # ------------------------------------------- # EKS # ------------------------------------------- variable "eks_cluster_name" { default = "eks-scraly-cluster" } variable "eks_version" { default = "1.15" # Kubernetes v1.15 } variable "eks_resource_prefix" { default = "eks-scraly-cluster" } variable "eks_public_access_cidrs" { description = "List of CIDR blocks which can access the EKS public API server endpoint when enabled" default = [ "xx.xx.xx.xx/32", "xx.xx.xx.xx/32" ] } # ------------------------------------------- # Tags # ------------------------------------------- variable "tag_application" { default = "eks-scraly-cluster" } variable "tag_contact" { default = "scraly@mail.com" }
Наши ресурсы были определены, так что теперь единственное, что нужно сделать, это развернуть нашу инфраю с этими следующими командными линиями:
$ cd "terraform/" $ terraform init -reconfigure # Apply $ terraform apply -input=false -auto-approve data.external.thumb: Refreshing state... data.aws_caller_identity.current: Refreshing state... data.aws_route_table.eks-route-table-public: Refreshing state... data.aws_availability_zones.az_list: Refreshing state... data.aws_internet_gateway.internet-gateway: Refreshing state... data.aws_route_table.eks-route-table-private[1]: Refreshing state... data.aws_route_table.eks-route-table-private[2]: Refreshing state... data.aws_route_table.eks-route-table-private[0]: Refreshing state... data.aws_route53_zone.scraly-hosted-zone: Refreshing state... data.aws_nat_gateway.nat-gateway[0]: Refreshing state... data.aws_nat_gateway.nat-gateway[2]: Refreshing state... data.aws_nat_gateway.nat-gateway[1]: Refreshing state... data.aws_vpc.eks-vpc: Refreshing state... aws_iam_policy.certmanager_route53_iam_policy: Creating... ... aws_eks_node_group.eks-scraly-cluster-node-group: Still creating... [5m40s elapsed] aws_eks_node_group.eks-scraly-cluster-node-group: Creation complete after 5m50s [id=eks-scraly-cluster:eks-scraly-cluster-node-group] Apply complete! Resources: 33 added, 0 changed, 0 destroyed. Outputs: arn_external_dns_role = arn:aws:iam:::role/eks-scraly-cluster-ServiceRole-ExternalDns endpoint = https://1234567891234.ab1.eu-central-1.eks.amazonaws.com kubeconfig = ...
Круто, у нас есть кластер EKS Kubernetes, развернутый в нашей учетной записи AWS!
Давайте подтвердим это в вашем интерфейсе консоли AWS (или с AWS CLI).
Pfiou … это было не проще, чем с EKSCTL, но с Terraform теперь мы можем развернуть множественные кластеры EKS с различными переменными, в разных средах, с разными версиями Kubernetes, в разных сетевых VPC/IG/подсетях…
Давайте перейдем к второй части этой статьи для конфигурации EKS, развертывание Traefik, внешнее DNS, HTTPBIN, обеспеченные через MTLS.
И если вы заинтересованы в Terraform, я создал шпаргалку:
https://github.com/scraly/terraform-cheat-sheet/blob/master/terraform-cheat-sheet.pdf
Оригинал: «https://dev.to/aurelievache/deploying-a-kubernetes-cluster-aws-eks-an-api-gateway-secured-by-mtls-with-terraform-external-dns-traefik-part-1-34nd»