Ця стаття не про те як потрібно організовувати код Terraform і якими правильними враперами його потрібно обкласти, а лише представлений якомога простіший опис інсталяції всіх необхідних ресурсів.
1. EXTERNAL DNS
Контролер, що слідкує за K8s сервісами та інгресами, та у разі необхідності створює записи на стороні DNS-провайдера. У нашому випадку це буде AWS Route53, проте ExternalDNS також підтримує роботу із багатьма іншими рішеннями як cloud-hosted (AzureDNS, CloudFlare чи DigitalOcean), так і з деякими self-hosted (CoreDNS, PowerDNS, Bind, Windows DNS). Із повним переліком можна ознайомитись за цим посиланням. Перейдемо до опису його установки:
$ cd ../addons/external-dns
$ cat main.tf
data "terraform_remote_state" "eks" {
backend = "s3"
config = {
bucket = "my-tf-state-2023-06-01"
key = "my-eks.tfstate"
region = "us-east-1"
}
}
data "aws_route53_zone" "this" {
for_each = toset(var.route53_zone_ids)
zone_id = each.key
}
locals {
route53_zone_arns = [for k, v in data.aws_route53_zone.this : v.arn]
}
module "external_dns_pod_identity" {
source = "terraform-aws-modules/eks-pod-identity/aws"
version = "v1.4.1"
name = "external-dns"
attach_external_dns_policy = true
# Array like ["arn:aws:route53:::hostedzone/Z04055631FR4AIE80F1GK",
# "arn:aws:route53:::hostedzone/Z056915018W8C59H17J0X"]
external_dns_hosted_zone_arns = local.route53_zone_arns
# Pod Identity Associations
association_defaults = {
namespace = "kube-system"
service_account = "external-dns"
}
associations = {
one = {
cluster_name = data.terraform_remote_state.eks.outputs.cluster_name
}
}
}
resource "helm_release" "this" {
name = "external-dns"
repository = "oci://registry-1.docker.io/bitnamicharts"
chart = "external-dns"
version = var.helm_package_version
namespace = "kube-system"
set {
name = "serviceAccount.create"
value = "true"
}
set {
name = "provider"
value = "aws"
}
set {
name = "txtOwnerId"
value = "External-dns addon of ${data.terraform_remote_state.eks.outputs.cluster_name} EKS cluster"
}
set {
name = "zoneIdFilters"
# list like "{Z04055631FR4AIE80F1GK,Z056915018W8C59H17J0X}"
value = format("{%s}", join(",", var.route53_zone_ids))
}
# it removes unused domains after removing related resources
set {
name = "policy"
value = "sync"
}
}
$ cat variables.tf
variable "region" {
description = "AWS region"
type = string
default = "us-east-1"
}
variable "credentials" {
default = ["~/.aws/credentials"]
description = "where your access and secret_key are stored, you create the file when you run the aws config"
}
variable "helm_package_version" {
description = "Version of the helm package."
type = string
default = "8.3.8"
}
variable "route53_zone_ids" {
description = "Route53 zone IDs to be served by external-dns addon."
type = list(string)
default = ["Z04055631FR4AIE80F1GK", "Z056915018W8C59H17J0X"]
}
Зроблю певні пояснення до коду:
- у main.tf ми описуємо використання стейта раніше створеного EKS-кластеру, адже його дані необхідні для ролі, конфігурації самого аддону та асоціації ролі для нього через pod identity
- за допомогою стороннього модуля створимо IAM роль та її асоціацію через pod identity, котра буде прив'язана до імені сервіс акаунту та простору імен, в котрому він розміщений. Робота додатку через IRSA також можлива і приведена в файлі main.tf_irsa
- і останній крок - установка Helm-чарту ExternalDNS в kube-system простір імен. Якось так склалось, що я використовую чарт від Bitnami проте є також офіційний, котрий бажано і використовувати
- також не забуваємо виправити наведені жирним рядки на власні
Запустимо цей terraform-код:
$ terraform init
$ terraform apply
Після установки перевіримо чи працює ExternalDNS (не забуваємо підставити свою назву кластера):
$ aws eks update-kubeconfig --region us-east-1 --name my-eks-NbP3tleo
$ kubectl get pods -n kube-system | grep external-dns
external-dns-55c57c5b9-wxzl2 1/1 Running 0 9m34s
$ kubectl -n kube-system logs -f deploy/external-dns
time="2023-11-25T16:13:31Z" level=info msg="Applying provider record filter for domains: [example1.com. .example1.com. example2.com. .example2.com.]"
time="2023-11-25T16:13:31Z" level=info msg="All records are already up to date"
ExternalDNS працює як із Kubernetes сервісами, так із інгресами. Цього разі розглянемо створення DNS-записів для інгресу із залученням додаткової анотації:
$ kubectl apply -f - <<EOF
apiVersion: v1
kind: Namespace
metadata:
name: echoserver
---
apiVersion: v1
kind: Service
metadata:
name: echoserver
namespace: echoserver
spec:
ports:
- port: 8080
targetPort: 8080
protocol: TCP
selector:
app: echoserver
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: echoserver
namespace: echoserver
spec:
selector:
matchLabels:
app: echoserver
replicas: 2
template:
metadata:
labels:
app: echoserver
spec:
containers:
- image: k8s.gcr.io/e2e-test-images/echoserver:2.5
imagePullPolicy: Always
name: echoserver
ports:
- containerPort: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: echoserver
namespace: echoserver
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/group.name: my-group
external-dns.alpha.kubernetes.io/hostname: echoserver.example1.com
spec:
ingressClassName: alb
rules:
- http:
paths:
- path: /
pathType: Exact
backend:
service:
name: echoserver
port:
number: 8080
EOF
namespace/echoserver created
service/echoserver created
deployment.apps/echoserver created
ingress.networking.k8s.io/echoserver created
Логи:
$ kubectl -n kube-system logs -f deploy/external-dns
...
time="2023-11-25T16:25:37Z" level=info msg="Desired change: CREATE cname-echoserver.example1.com TXT [Id: /hostedzone/ZONE_ID_1]"
time="2023-11-25T16:25:37Z" level=info msg="Desired change: CREATE echoserver.example1.com A [Id: /hostedzone/ZONE_ID_1]"
time="2023-11-25T16:25:37Z" level=info msg="Desired change: CREATE echoserver.example1.com TXT [Id: /hostedzone/ZONE_ID_1]"
time="2023-11-25T16:25:37Z" level=info msg="3 record(s) in zone example1.com. [Id: /hostedzone/ZONE_ID_1] were successfully updated"
$ kubectl get ing -n echoserver
NAME CLASS HOSTS ADDRESS PORTS AGE
echoserver alb * k8s-mygroup-064002fe58-2021024154.us-east-1.elb.amazonaws.com 80 9m1s
В Route53 буде створено alias-запис echoserver.example1.com, що буде вести на IP-адреси домену ALB k8s-mygroup-064002fe58-2021024154.us-east-1.elb.amazonaws.com за яким і буде знаходитись поди echoserver.
Відповідно за адресою http://echoserver.example1.com має відкриватись сторінка нашого додатку.
За замовчуванням externalDNS дивиться як в додаткові анотації, так і в hosts поле інгресу. І вже на основі цих даних створює DNS-записи. Якщо ж виставити значення додаткової анотації external-dns.alpha.kubernetes.io/ingress-hostname-source в значення defined-hosts-only чи annotation-only, то будуть читатись лише hosts поле або анотації відповідно.
ExternalDNS використовує додаткові TXT-записи в зоні для того, щоб трекати власноруч створені записи, проте підтримуються і інші сховища для зберігання сервісних даних, наприклад DynamoDB. Створені TXT записи в зоні Route53 можна побачити на скріншоті приведеному вище.
Посилання:
https://github.com/kubernetes-sigs/external-dns/blob/master/docs/tutorials/aws.md
https://repost.aws/knowledge-center/eks-set-up-externaldns
https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.5/guide/integrations/external_dns
https://tech.polyconseil.fr/external-dns-helm-terraform.html
2. EBS CSI DRIVER
Драйвер, що необхідний для використання AWS EBS у якості сховища для томів Kubernetes. Файли на диску кожного поду по-замовчуванню є ефемерними, тобто втрачатимуться після кожного перевантаження чи зупинки поду. Якщо ж додаток зберігає стан своєї роботи, то цього буде недостатньо, адже втрата даних буде критичною. Тож такий додаток має змонтувати знову той самий EBS/Kubernetes Volume у разі якихось перевантажень, оновлень чи інших змін/негараздів.
Дистрибуцією EBS CSI драйвера займається сам AWS, проте IAM-роль для його роботи потрібно створювати власноруч, що доволі дивно.
$ cd ../ebs-csi
$ cat main.tf
data "terraform_remote_state" "eks" {
backend = "s3"
config = {
bucket = "my-tf-state-2023-06-01"
key = "my-eks.tfstate"
region = "us-east-1"
}
}
module "aws_ebs_csi_pod_identity" {
source = "terraform-aws-modules/eks-pod-identity/aws"
version = "v1.5.0"
name = "aws-ebs-csi"
attach_aws_ebs_csi_policy = true
# if you need encryption (you have to need it)
# aws_ebs_csi_kms_arns = ["arn:aws:kms:*:*:key/1234abcd-12ab-34cd-56ef-1234567890ab"]
association_defaults = {
namespace = "kube-system"
service_account = "ebs-csi-controller-sa"
}
associations = {
one = {
cluster_name = data.terraform_remote_state.eks.outputs.cluster_name
}
}
}
resource "aws_eks_addon" "this" {
cluster_name = data.terraform_remote_state.eks.outputs.cluster_name
addon_name = "aws-ebs-csi-driver"
addon_version = var.addon_version
# service_account_role_arn = module.irsa_role.iam_role_arn
}
data "kubectl_path_documents" "storage_class" {
pattern = "templates/sc.tpl.yaml"
vars = {
# Produces string like "env_name=development,region=us-east-1,team=devops"
tag_specifications = join(",", [for key, value in var.volume_tags : "${key}=${value}"])
}
}
resource "kubectl_manifest" "storage_class" {
count = length(data.kubectl_path_documents.storage_class.documents)
yaml_body = element(data.kubectl_path_documents.storage_class.documents, count.index)
}
Та перелік змінних, що будуть відправлені на основний код:
$ cat variables.tf
variable "region" {
description = "AWS region"
type = string
default = "us-east-1"
}
variable "credentials" {
default = ["~/.aws/credentials"]
description = "where your access and secret_key are stored, you create the file when you run the aws config"
}
variable "addon_version" {
type = string
description = "Version of EKS addon."
# you can use "latest" here
default = "v1.34.0-eksbuild.1"
}
variable "volume_tags" {
type = map(any)
description = "Volume tags."
# Can't be changed w/o recreating of StorageClass K8s object
default = {
env_name = "development"
region = "us-east-1"
team = "devops"
}
}
Традиційно прокоментую логіку, хоч вона не сильно відрізніється від попереднього аддону:
- вичитуємо стейт створеного EKS кластеру
- створюємо IAM роль та pod identity асоціацію для ServiceAccount-а. Приклад зі створенням IRSA приведений у файлі main.tf_irsa і для його роботи потрібен робочий OIDC провайдер
- terraform ресурс aws_eks_addon активує addon/драйвер до нашого EKS кластеру через AWS API
- kubectl_manifest створить новий StorageClass, котрий описаний за відносною адресою templates/sc.yaml.tpl. Він виступає у якості шаблону для майбутніх томів
Запустимо Тераформ код:
$ terraform init
$ terraform apply
$ kubectl get StorageClass
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
ebs-generic ebs.csi.aws.com Delete WaitForFirstConsumer false 91s
gp2 (default) kubernetes.io/aws-ebs Delete WaitForFirstConsumer false 7h42m
Перший StorageClass створений нами, а наступний - це in-tree реалізація EBS драйвера в межах кодової бази Kubernetes. Він активується одразу із встановленням кластеру. Наразі він вже не підтримується, але і поки планів по його видаленню немає.
$ kubectl get sc ebs-generic -o yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
...
creationTimestamp: "2023-11-25T23:17:42Z"
name: ebs-generic
resourceVersion: "94073"
uid: 8441148f-6a7c-4c79-abdd-724c2be2b124
parameters:
allowAutoIOPSPerGBIncrease: "true"
csi.storage.k8s.io/fstype: ext4
encrypted: "true"
iopsPerGB: "3"
tagSpecification_0: env_name=development
tagSpecification_1: region=us-east-1
tagSpecification_2: team=devops
throughput: "250"
type: gp3
provisioner: ebs.csi.aws.com
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
Перевіримо чи він справді працює. Для цього створимо об'єкт PersistentVolumeClaim та підключимо його до поду:
$ kubectl apply -f - <<EOF
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: ebs-claim-2
spec:
accessModes:
- ReadWriteOnce
storageClassName: ebs-generic
resources:
requests:
storage: 2Gi
---
apiVersion: v1
kind: Pod
metadata:
name: app-2
spec:
containers:
- name: app-2
image: centos
command: ["/bin/sh"]
args: ["-c", "while true; do echo $(date -u) >> /data/out.txt; sleep 5; done"]
volumeMounts:
- name: persistent-storage
mountPath: /data
volumes:
- name: persistent-storage
persistentVolumeClaim:
claimName: ebs-claim-2
EOF
Чи справді том підключений до поду:
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-8ef428ae-ba63-4f4e-9746-7488c8f8dd07 2Gi RWO Delete Bound default/ebs-claim-2 ebs-generic 37s
$ kubectl describe pv pvc-8ef428ae-ba63-4f4e-9746-7488c8f8dd07
$ kubectl exec -it app-2 -- cat /data/out.txt
неділя, 26 листопада 2023 00:01:54 +0000
неділя, 26 листопада 2023 00:01:54 +0000
неділя, 26 листопада 2023 00:01:54 +0000
неділя, 26 листопада 2023 00:01:54 +0000
$ kubectl exec -it app-2 -- df
Filesystem 1K-blocks Used Available Use% Mounted on
overlay 20959212 6004804 14954408 29% /
tmpfs 65536 0 65536 0% /dev
tmpfs 985256 0 985256 0% /sys/fs/cgroup
/dev/nvme1n1 1992552 28 1976140 1% /data
/dev/nvme0n1p1 20959212 6004804 14954408 29% /etc/hosts
shm 65536 0 65536 0% /dev/shm
tmpfs 1483088 12 1483076 1% /run/secrets/kubernetes.io/serviceaccount
tmpfs 985256 0 985256 0% /proc/acpi
tmpfs 985256 0 985256 0% /sys/firmware
EBS CSI контролер може бути запущений на Fargate-воркерах, проте атачити диски можна буде лише до подів, що працюють на EC2-інстансах. Це варто мати на увазі.
За наступним посиланням можна подивитись приклади роботи із EBS вольюмами в EKS, а саме зміна його розміру, зняття снепшоту, робота із OS Windows та інше.
Посилання:
https://github.com/kubernetes-sigs/aws-ebs-csi-driver/tree/master/docs
https://github.com/kubernetes-sigs/aws-ebs-csi-driver/tree/master/examples/kubernetes/dynamic-provisioning
https://docs.aws.amazon.com/eks/latest/userguide/ebs-csi.html
https://docs.aws.amazon.com/eks/latest/userguide/ebs-sample-app.html
https://github.com/kubernetes-sigs/aws-ebs-csi-driver/blob/master/docs/modify-volume.md
3. METRICS SERVER
Джерело метрик контейнерів для цілей автоматичного масштабування. Цей аддон збирає ресурсні метрики від Kubelet та надає їх через Metrics API для їхнього використання Horizontal Pod та Vertical Pod скейлерами.
Metrics Server не можна використовувати для інших цілей, окрім зазначених, наприклад моніторингу. Для цього є kube-state-metrics, котрий зазвичай встановлюється разом із Prometheus оператором.
Код його установки для EKS є доволі простим, адже не потребує додаткових ролей:
$ cd ../metrics-server
$ cat main.tf
data "terraform_remote_state" "eks" {
backend = "s3"
config = {
bucket = "my-tf-state-2023-06-01"
key = "my-eks.tfstate"
region = "us-east-1"
}
}
resource "helm_release" "this" {
name = "metrics-servers"
repository = "https://kubernetes-sigs.github.io/metrics-server/"
chart = "metrics-server"
version = var.helm_package_version
namespace = var.namespace
set {
name = "replicas"
value = 2
}
}
Metrics Server на відміну від ExternalDNS, підтримує роботу в HA конфігурації.
$ cat variables.tf
variable "region" {
description = "AWS region"
type = string
default = "us-east-1"
}
variable "credentials" {
default = ["~/.aws/credentials"]
description = "where your access and secret_key are stored, you create the file when you run the aws config"
}
variable "helm_package_version" {
description = "Helm package version."
type = string
default = "3.12.1"
}
variable "namespace" {
description = "Namespace service will be deployed to."
type = string
default = "kube-system"
}
Застосуємо код:
$ terraform init
$ terraform apply
Metrics Server імплементує також вивід команди kubectl top. Тож якщо вона працює - значить аддон встановлено коректно:
$ kubectl top nodes
NAME CPU(cores) CPU% MEMORY(bytes) MEMORY%
ip-10-196-0-139.eu-west-1.compute.internal 125m 3% 2831Mi 41%
ip-10-196-18-125.eu-west-1.compute.internal 213m 5% 2534Mi 37%
ip-10-196-19-22.eu-west-1.compute.internal 62m 1% 1945Mi 28%
$ kubectl top pod metrics-server-76494d95bd-fln5c -n kube-system
NAME CPU(cores) MEMORY(bytes)
metrics-server-76494d95bd-fln5c 5m 41Mi
4. EXTERNAL SECRETS OPERATOR
Це оператор, котрий інтегрує системи управління та зберігання секретів на кшталт AWS Secrets Manager, HashiCorp Vault, Google Secrets Manager, Azure Key Vault та багато інших. Оператор вичитує інформацію зі сторонніх API та автоматично інжектить їх як звичайні Kubernetes секрети.
External Secrets Operator імплементує додаткові CRD примітиви: ExternalSecret, SecretStore та ClusterSecretStore, що реалізують дружні до користувача абстракції над сторонніми сервісами. Детальніше про їх використання поговоримо після установки оператора.
Традиційно скористаємось Тераформом і офіційним helm-чартом:
$ cd ../external-secrets
$ cat main.tf
data "terraform_remote_state" "eks" {
backend = "s3"
config = {
bucket = "my-tf-state-2023-06-01"
key = "my-eks.tfstate"
region = "us-east-1"
}
}
module "external_secrets_pod_identity" {
source = "terraform-aws-modules/eks-pod-identity/aws"
version = "v1.5.0"
name = "external-secrets"
attach_external_secrets_policy = true
external_secrets_ssm_parameter_arns = var.external_secrets_ssm_parameter_arns
external_secrets_secrets_manager_arns = var.external_secrets_secrets_manager_arns
external_secrets_kms_key_arns = var.external_secrets_kms_key_arns
external_secrets_create_permission = true
association_defaults = {
namespace = "kube-system"
service_account = "external-secrets"
}
associations = {
one = {
cluster_name = data.terraform_remote_state.eks.outputs.cluster_name
}
}
}
resource "helm_release" "external_secrets" {
name = "external-secrets"
repository = "https://charts.external-secrets.io"
chart = "external-secrets"
version = var.helm_package_version
namespace = "kube-system"
set {
name = "serviceAccount.create"
value = "true"
}
}
IRSA-варіант коду знаходиться в main.tf_irsa, проте ми традиційно будемо користуватись pod identity асоціацією ролі.
$ cat variables.tf
variable "region" {
description = "AWS region"
type = string
default = "us-east-1"
}
variable "credentials" {
default = ["~/.aws/credentials"]
description = "where your access and secret_key are stored, you create the file when you run the aws config"
}
variable "helm_package_version" {
type = string
description = "Version of the helm package."
default = "0.10.3"
}
variable "external_secrets_ssm_parameter_arns" {
type = list(any)
description = "Parameter Store arns list."
default = ["arn:aws:ssm:us-east-1:*:parameter/dev/*"]
}
variable "external_secrets_secrets_manager_arns" {
type = list(any)
description = "Secrets manager arns list."
default = ["arn:aws:secretsmanager:us-east-1:*:secret:dev/*"]
}
variable "external_secrets_kms_key_arns" {
type = list(any)
description = "KMS keys arns for decoding."
default = ["arn:aws:kms:us-east-1:*:key/9a04d3c9-1589-42b3-9dca-296b6a51c695"]
}
Тут варто виправити на свої значення KMS-ключа (цей приклад із дефолтним ключем, котрий надає AWS, і тут варто ввести ключ свого акаунту) та адресу секретів. Логіка шляху dev/* в тому, що оператор зможе читати лише секрети під цією маскою.
Застосуємо код:
$ terraform init
$ terraform apply
Створимо тестовий секрет dev/secret в AWS Secrets Manager:
Пересвідчившись, що оператор працює коректно, створимо ClusterSecretStore із описом сервісу, що будемо використовувати для читання секретів:
$ kubectl apply -f - <<EOF
kind: ClusterSecretStore
apiVersion: external-secrets.io/v1beta1
metadata:
name: global-secret-store
spec:
provider:
aws:
service: SecretsManager
region: us-east-1
EOF
Можна скористатись також і SecretStore CRD, проте він працює лише в межах неймспейса, в якому був створений.
Такий опис провайдера aws буде працювати як для IRSA, так і для pod identity. Наступний же буде працювати лише для IRSA, бо під капотом він використовує aws.AssumeRoleWithWebIdentity() виклик:
$ kubectl apply -f - <<EOF
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: global-secret-store-jwt
spec:
provider:
aws:
service: SecretsManager
region: us-east-1
auth:
jwt:
serviceAccountRef:
name: external-secrets
namespace: kube-system
EOF
Окрім цього можна використовувати у якості доступу access-key та aws-secret, що може бути корисним у разі відсутності можливості використання ролі. Про це можна почитати детальніше за наступним посиланням.
Перевіримо чи створився global-secret-store:
$ kubectl get clustersecretstore -A
NAME AGE STATUS CAPABILITIES READY
global-secret-store 18h Valid ReadWrite True
Спробуємо прочитати записані поля секрету dev/secret:
$ kubectl apply -f - <<EOF
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: some-svc-secret
namespace: default
spec:
refreshInterval: 5m
secretStoreRef:
name: global-secret-store
kind: ClusterSecretStore
target:
name: some-svc-secret-es # secret name (will be mirrored as plain secret to K8s)
creationPolicy: Owner
data:
- secretKey: password
remoteRef:
key: dev/secret # secret name (on AWS side)
property: password # secret key name (on AWS side)
- secretKey: username
remoteRef:
key: "dev/secret" # secret name (on AWS side)
property: username # secret key name (on AWS side)
EOF
$ kubectl get es
NAME STORE REFRESH INTERVAL STATUS READY
some-svc-secret global-secret-store 20s SecretSynced True
Перевіримо чи поля password та username були відображені як Kubernetes секрет, котрий було вказано в ExternalSecret:
$ kubectl get secret
NAME TYPE DATA AGE
...
some-svc-secret-es Opaque 2 6m18s
$ kubectl edit secret some-svc-secret-es
Посилання:
https://external-secrets.io/main/
https://external-secrets.io/latest/introduction/faq/
https://cloudhero.io/getting-started-with-external-secrets-operator-on-kubernetes-using-aws-secrets-manager/
https://aws.amazon.com/blogs/containers/leverage-aws-secrets-stores-from-eks-fargate-with-external-secrets-operator/
https://www.giantswarm.io/blog/manage-kubernetes-secrets-using-aws-secrets-manager
https://github.com/external-secrets/external-secrets/issues/2951
https://github.com/external-secrets/external-secrets/issues/478#issuecomment-964413129
5. AWS SECRETS AND CONFIGURATION PROVIDER (ASCP)
На відміну від External Secrets Operator (ESO), окрім синхронізації із K8s секретами, цей спосіб також дозволяє відображати AWS Secrets Manager/Parameter Store секрети як файли файлової системи, що змонтовані в под EKS кластеру. ASCP працює лише із EC2 нод групами і не підтримує Fargate. Функціонал автоматичних ротацій змонтованих секретів також присутній, але він наразі перебуває в статусі альфа.
ASCP для своєї роботи потребує Kubernetes Secrets Store CSI Driver. Останній в свою чергу підтримує і інші провайдери секретів, окрім AWS.
ASCP працює лише із IRSA і модуля для її установки цього разу ніхто не написав:
$ cd ../ascp
$ cat main.tf
data "terraform_remote_state" "eks" {
backend = "s3"
config = {
bucket = "my-tf-state-2023-06-01"
key = "my-eks.tfstate"
region = "us-east-1"
}
}
resource "aws_iam_policy" "this" {
name = "eks-ascp-${data.terraform_remote_state.eks.outputs.cluster_name}-${var.region}"
path = "/"
description = "AWS ASCP controller IAM policy."
policy = file(format("%s/templates/policy.json", path.module))
}
resource "aws_iam_role" "this" {
name = "eks-ascp-${data.terraform_remote_state.eks.outputs.cluster_name}-${var.region}"
assume_role_policy = templatefile("${path.module}/templates/assume_policy.json_tpl",
{
oidc_provider = data.terraform_remote_state.eks.outputs.oidc_provider,
oidc_provider_arn = data.terraform_remote_state.eks.outputs.oidc_provider_arn
})
}
resource "aws_iam_role_policy_attachment" "attach" {
role = aws_iam_role.this.name
policy_arn = aws_iam_policy.this.arn
}
resource "helm_release" "csi_secrets_store" {
name = "csi-secrets-store"
repository = "https://kubernetes-sigs.github.io/secrets-store-csi-driver/charts"
chart = "secrets-store-csi-driver"
version = var.csi_secrets_store_helm_version
namespace = "kube-system"
}
# AWS Secrets and Configuration Provider (ASCP)
resource "helm_release" "aws_secrets_manager" {
name = "secrets-store-csi-driver-provider-aws"
repository = "https://aws.github.io/secrets-store-csi-driver-provider-aws"
chart = "secrets-store-csi-driver-provider-aws"
version = var.aws_secrets_manager_helm_version
namespace = "kube-system"
}
Тут ми встановили 2 хелм чарти та написали для них trusted policy:
$ cat templates/assume_policy.json_tpl
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "${oidc_provider_arn}"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"${oidc_provider}:aud": "sts.amazonaws.com",
"${oidc_provider}:sub": "system:serviceaccount:default:app-ascp-sa"
}
}
}
]
}
Тобто сервіс аккаунт додатку, котрий потребуватиме секрет, має лежати в default неймспейсі і мати назву app-ascp-sa. Звісно це можна змінювати на власний розсуд, додавати маски і цим робити правила більш загальними. IAM полісі буде виглядати так:
$ cat templates/policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret"
],
"Resource": [
"arn:aws:secretsmanager:us-east-1:*:secret:dev/*",
]
}
]
}
$ cat variables.tf
variable "region" {
description = "AWS region"
type = string
default = "us-east-1"
}
variable "credentials" {
default = ["~/.aws/credentials"]
description = "where your access and secret_key are stored, you create the file when you run the aws config."
}
variable "csi_secrets_store_helm_version" {
default = "1.4.5"
description = "Helm package version of CSI Secret Store"
}
variable "aws_secrets_manager_helm_version" {
default = "0.3.9"
description = "Helm package version of AWS Secret Manager"
}
Цього разу ми дозволили читання подом всіх секретів із маскою secret:dev/* в регіоні us-east-1.
Застосуємо код:
$ terraform init
$ terraform apply
Створимо тестовий секрет dev/secret в AWS Secrets Manager, абсолютно аналогічний як в попередньому прикладі із ESO, та перейдемо до дій.
$ kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-ascp
spec:
selector:
matchLabels:
app: app-ascp
replicas: 1
template:
metadata:
labels:
app: app-ascp
spec:
containers:
- image: centos
command: ["/bin/sh"]
args: ["-c", "sleep 1800"]
name: app-ascp
volumeMounts:
- name: secrets-store-inline
mountPath: "/conf/"
readOnly: true
serviceAccountName: app-ascp-sa
volumes:
- name: secrets-store-inline
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: "app-ascp-aws-secret"
---
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: app-ascp-aws-secret
spec:
provider: aws
parameters:
objects: |
- objectName: "dev/secret"
objectType: "secretsmanager"
objectAlias: "secret.yaml"
---
apiVersion: v1
kind: ServiceAccount
metadata:
annotations:
# your role needs to be here
eks.amazonaws.com/role-arn: arn:aws:iam::789248082627:role/eks-ascp-my-eks-yXHoHQxx-us-east-1
name: app-ascp-sa
EOF
---
deployment.apps/app-ascp created
secretproviderclass.secrets-store.csi.x-k8s.io/app-ascp-aws-secret created
serviceaccount/app-ascp-sa created
Наведену роль необхідно змінити на власну. Тепер спробуємо знайти та переглянути наш секрет:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
app-ascp-695cbf7f8c-tg22q 1/1 Running 0 44s
$ kubectl exec -it app-ascp-695cbf7f8c-tg22q -- ls -la /conf/
total 4
drwxrwxrwt 2 root root 60 Oct 8 13:23 .
drwxr-xr-x 1 root root 29 Oct 8 13:23 ..
-rw-r--r-- 1 root root 42 Oct 8 13:23 secret.yaml
$ kubectl exec -it app-ascp-695cbf7f8c-tg22q -- cat /conf/secret.yaml
{"username":"admin","password":"admin123"}%
Посилання:
https://docs.aws.amazon.com/secretsmanager/latest/userguide/integrating_csi_driver.html
https://secrets-store-csi-driver.sigs.k8s.io/
Немає коментарів:
Дописати коментар