Однажды команда, которая хочет иметь выделенный кластер 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»