1주차 - 테라폼으로 프로비저닝, 다양한 노드 활용해보기
개요
이번에는 Terraform을 이용해서 배포하는 것을 실습해보자.
다음의 목표를 가지고 있다.
- 테라폼을 활용하여 기본적인 eks 재현
- fargate 프로필을 통한 파게이트 파드 배치
- bottlerocket ami 사용 후 비교
- private ecr 연동
연습
이번에는 eks를 통해 모든 것이 알아서 만들어지도록 구성할 것이다.
그러기 이전에, 간단하게 vpc를 기본 구성하는 연습을 해보자.
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.16"
}
}
required_version = ">= 1.2.0"
}
variable "cluster_name" {
default = "terraform-eks"
}
provider "aws" {
shared_config_files = ["~/.aws/config"]
shared_credentials_files = ["~/.aws/credentials"]
default_tags {
tags = {
org = "aews"
}
}
}
resource "aws_vpc" "vpc" {
cidr_block = "192.168.0.0/16"
instance_tenancy = "default"
tags = {
Name = "${var.cluster_name}-VPC"
}
}
resource "aws_subnet" "PublicSubnet1" {
vpc_id = aws_vpc.vpc.id
availability_zone = "ap-northeast-2a"
cidr_block = "192.168.1.0/24"
tags = {
Name = "${var.cluster_name}-PublicSubnet1"
}
}
resource "aws_subnet" "PublicSubnet2" {
vpc_id = aws_vpc.vpc.id
availability_zone = "ap-northeast-2c"
cidr_block = "192.168.2.0/24"
tags = {
Name = "${var.cluster_name}-PublicSubnet2"
}
}
resource "aws_subnet" "PrivateSubnet1" {
vpc_id = aws_vpc.vpc.id
availability_zone = "ap-northeast-2a"
cidr_block = "192.168.3.0/24"
tags = {
Name = "${var.cluster_name}-PrivateSubnet1"
}
}
resource "aws_subnet" "PrivateSubnet2" {
vpc_id = aws_vpc.vpc.id
availability_zone = "ap-northeast-2c"
cidr_block = "192.168.4.0/24"
tags = {
Name = "${var.cluster_name}-PrivateSubnet2"
}
}
resource "aws_internet_gateway" "gw" {
vpc_id = aws_vpc.vpc.id
}
# resource "aws_internet_gateway_attachment" "igw_attach" {
# internet_gateway_id = aws_internet_gateway.gw.id
# vpc_id = aws_vpc.vpc.id
# }
resource "aws_route_table" "pubrtt" {
vpc_id = aws_vpc.vpc.id
}
resource "aws_route" "pubsubrt" {
route_table_id = aws_route_table.pubrtt.id
destination_cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.gw.id
}
resource "aws_route_table_association" "pub1rtasso" {
subnet_id = aws_subnet.PublicSubnet1.id
route_table_id = aws_route_table.pubrtt.id
}
resource "aws_route_table_association" "pub2rtasso" {
subnet_id = aws_subnet.PublicSubnet2.id
route_table_id = aws_route_table.pubrtt.id
}
resource "aws_route_table" "privrtt" {
vpc_id = aws_vpc.vpc.id
}
resource "aws_route_table_association" "priv1rtasso" {
subnet_id = aws_subnet.PrivateSubnet1.id
route_table_id = aws_route_table.privrtt.id
}
resource "aws_route_table_association" "priv2rtasso" {
subnet_id = aws_subnet.PrivateSubnet2.id
route_table_id = aws_route_table.privrtt.id
}
이번이 거의 두번째 사용이라 아직 익숙하지는 않다.
그래서 문서를 보면서 최대한 구현에만 집중해본다.
기존의 클포 양식을 보고 따라해봤다.
terraform plan
terraform apply
얼추 잘 만들어진 것 같다!
이걸 그대로 사용해도 되나, 퍼블릭하게 제공되는 vpc 모듈을 사용해도 된다.
기왕 만들었으니, 이걸 모듈로 사용하자.
eks 구성
https://github.com/terraform-aws-modules/terraform-aws-eks/blob/v17.24.0/aws_auth.tf
여기에 다양한 정보가 들어가있다.
예시가 많아서 좋다.
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
# version = "~> 4.16"
}
}
# required_version = ">= 1.2.0"
}
variable "cluster_name" {
default = "terraform-eks"
}
provider "aws" {
shared_config_files = ["~/.aws/config"]
shared_credentials_files = ["~/.aws/credentials"]
default_tags {
tags = {
org = "aews"
}
}
}
module "eks" {
source = "terraform-aws-modules/eks/aws"
# version = "20.33.1"
}
아직 버전 호환성에 대한 명확한 지식이 부족해서 버전을 최대한 생략하고 초기화를 진행해본다.
eks 모듈이 있으니 이걸 적극 활용해보자.[1]
terraform init -upgrade
본격적으로 해보니까, vpc는 직접 설정을 해줘야 하는 모양이다.
이건 eksctl과 또 다른 것 같다.
아직 어리숙한 게 많다..
이건 그냥 단순히 내가 퍼블릭 서브넷을 같은 az에 두 개 만든 실수였다.
7분 정도 걸려서 eks 클러스터가 만들어졌으나, 이후에 노드 그룹 관련 설정에서 에러가 발생했다.
map_public_ip_on_launch = each.value.type == "Public" ? true : null
퍼블릭 서브넷에 배치될 경우 자동으로 퍼블릭 ip를 받을 수 있도록 설정해주면 된다.
eks 모듈 인풋 중에..이런 것도 있다..
아래 [[#노드 그룹 생성 실패]]에 대한 문제가 있는 것을 차치하고 기본적인 분석을 해보자.
혹시나 해서 봤는데, 이거 기본 애드온 설치가 안 되고 있다.
이전과 똑같이 하려면 직접 애드온도 명시를 해주는 것이 좋을 것 같다.
aws eks describe-addon-versions --kubernetes-version 1.31 --query 'addons[].{MarketplaceProductUrl: marketplaceInformation.productUrl, Name: addonName, Owner: owner Publisher: publisher, Type: type}' --output table
사용 가능한 애드온 이름을 검색한다.
사실 coredns를 안 쓰는 곳이 있나?
이게 굳이 애드온인 이유는 잘 모르겠다.
프록시랑 cni는 당연히 그럴 수 있다 보지만...
노드 그룹 생성 실패
프로비저닝은 정상적으로 되는 것 같았다.
실제로 인스턴스들도 잘 생성이 되었으나.. 뭔가 다른 이슈가 있는 것 같다.
노드 그룹 설정에 문제가 있었을까?
16분이 지나도록 기다리고 있다.
그러나 콘솔에서는 만들어진 것으로 나오는 것 같다.
엔드포인트 접근 시에도 문제 없이 되기는 한다.
24분 기다리더니 그냥 fail이 떴다.
이제 보니까 노드 그룹 생성 실패로 표기가 되고 있다.
비슷한 에러에 대한 글을 찾았다[2]
다양한 분석이 이뤄졌는데, 대체로 cni role에 대한 것으로 추측되고 있다.
module "vpc_cni_irsa" {
source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"
version = "~> 4.12"
role_name_prefix = "VPC-CNI-IRSA"
attach_vpc_cni_policy = true
vpc_cni_enable_ipv4 = true # NOTE: This was what needed to be added
oidc_providers = {
main = {
provider_arn = module.eks.oidc_provider_arn
namespace_service_accounts = ["kube-system:aws-node"]
}
}
}
직접 모듈 받아온 후 관련한 롤 설정을 하는 것으로 보인다.
핵심은 attach_vpc_cni_policy = true
이다.
바로 아래에서 알게 됐는데, 이 방식은 내 케이스를 해결하는 방식이 아니었다.
이에 대한 faq 내용도 있는 것 같다.[3]
퍼블릭 엔드포인트에 대해서, 나는 집 ip만 설정해뒀는데, 그것 때문에 이슈가 발생하고 있을 가능성이 있다.
그러나 prvate access도 true를 걸어뒀는데, 이 경우에는 액세스 모드가 혼용으로 동작해서 노드 그룹이 정상적으로 만들어져야 한다고 생각이 들었다.
그리고 여기에 아마 노드 그룹 생성에 실패가 뜨는 원인이 있지 않을까 하는 생각도 든다.
public access가 true가 돼있는 이상 일단 node join에 대해 퍼블릭 엔드포인트를 사용하는 게 아닐까?
그러면 당연히 실패할 것이다.
그러나 실상 해당 요청이 실패한 이후 private access를 시도하여 노드 조인을 성공시키나 이 정보를 테라폼이 제대로 반환받지 못한다면, 클러스터는 제대로 구성이 되더라도 테라폼에서는 이를 확인하지 못하는 상황이 그대로 연출될 것이다.
이 가설을 확인해보자.
가능한 해결책
- 퍼블릭 cidr 범위를 일단 다 풀어둔다.
- 프라이빗 모드만 활성화시켜서 처음부터 프라이빗 대역을 쓰게 만든다.
퍼블릭 cidr 범위를 풀어보니 제대로 완성이 되었다.
이거 처리할 방법을 생각해봐야할 것 같다.
처음에는 무조건 퍼블릭으로 만들었다가 수정을 해야 하는가?
그런 방식은 너무 비효율적이다.
kubeconfig 자동으로 꺼내기
내 로컬에서 바로 통신할 수 있도록, kubecofig 파일을 꺼내는 작업을 만들어보고 싶다.
기본이 되는 최신 eks 모듈에서는 kubeconfig 파일을 output을 제공하지 않는다.
그런데 v17.24.0에서는 제공해주는 것 같다.[4]
몇 년 전 버전이라 현재 eks와 호환이 되지 않을 가능성이 있어서, 일단 시도는 해보고, 내가 직접 꺼낼 수 있도록 커스텀을 해보자.
apiVersion: v1
preferences: {}
kind: Config
clusters:
- cluster:
server: ${endpoint}
certificate-authority-data: ${cluster_auth_base64}
name: ${kubeconfig_name}
contexts:
- context:
cluster: ${kubeconfig_name}
user: aews-user
name: ${kubeconfig_name}
current-context: ${kubeconfig_name}
users:
- name: aews-user
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
args:
- --region
- ap-northeast-2
- eks
- get-token
- --cluster-name
- ${cluster_name}
- --output
- json
command: aws
env: null
interactiveMode: IfAvailable
provideClusterInfo: false
일단 간단한 형태로 kubeconfig 파일을 만들어봤다.
테라폼이 아직 익숙치 않은데, tpl 확장자로 선언하고 이를 나중에 local 같은 변수에서 불러서 각 변수들을 채워넣어서 완성시킨다.
kubeconfig = var.write_kubeconfig ? templatefile("${path.module}/templates/kubeconfig.tpl", {
cluster_name = local.cluster_name
kubeconfig_name = "cluster_eks"
endpoint = local.cluster_endpoint
cluster_auth_base64 = local.cluster_auth_base64
}) : ""
resource "local_file" "kubeconfig" {
content = local.kubeconfig
filename = "./kubeconfig"
# file_permission = var.kubeconfig_file_permission
directory_permission = "0755"
}
이런 식으로 만들었다.
아직 익숙하지 않은 관계로 조금 하드 코딩 식으로 만들었다.
적용이 완료되면 이렇게 로컬에 파일이 만들어진다.
그러면 이렇게 통신이 가능해진다!
eksctl utils write-kubeconfig --cluster terraform-eks
사실 eksctl을 설치했다면 이런 방법도 가능하긴 하다.
그러나 테라폼을 쓰는 이상 기왕이면 다른 툴을 혼용하지 않는 게 좋다고 생각한다.
아직 테라폼에 익숙하지 않으므로, 상세한 커스텀과 리팩토링은 후세에 맡기겠소..
노드에 ssh 접속하기
서브 모듈인 node group에 원격 접속에 대한 값을 설정할 수 있다.
resource "tls_private_key" "ssh_key" {
algorithm = "RSA"
rsa_bits = 4096
}
resource "aws_key_pair" "eks_key_pair" {
key_name = "eks-ssh-key"
public_key = tls_private_key.ssh_key.public_key_openssh
}
rsa 키를 만들고, 이 파일을 명시하도록 해본다.
eks_managed_node_groups = {
my_node = {
ami_type = "AL2_x86_64"
instance_types = ["t3.medium"]
desired_size = 2
min_size = 1
max_size = 3
use_custom_launch_template = false
remote_access = {
ec2_ssh_key = aws_key_pair.eks_key_pair.key_name
source_security_group_ids = [aws_security_group.eks_ssh_sg.id]
}
}
}
소스가 되는 보안 그룹 id를 지정하지 않아야 퍼블릭 접근이 가능해지는데, 이걸 괜히 설정했다가 아래에서 조금 삽질을 했다.
바스티온을 쓸 게 아니라면 source 부분은 쓰지 말자.
여기에서 중요한 것은 use_custom_launch_template
을 명시적으로 false 처리해줘야 한다는 것.
안 하니까 에러가 나더라.
이렇게 만들면 ssh를 위한 보안그룹이 인스턴스에 추가된다.
또한, 키페어가 잘 등록된다.
remoteaccess 보안 그룹이 생성됐다.
연결된 보안 그룹이 있는데, 이건 내가 source로 명시한 보안 그룹이다.
사실 이렇게 의도한 건 아니라, 급한 대로 직접 내 ip에서의 요청을 받을 수 있도록 룰을 추가했다.
분석
콘솔 확인
eks
eksctl로 설정했을 때와 뭔가 다르다.
eksctl에서는 내 유저가 노드를 확인할 수 없었는데, 여기에서는 가능하다.
(이 부분은 아직 확신할 수 없는 게, 이전 실습에서 실수로 root 계정 신원으로 eks를 만들었다.)
분명히 퍼블릭, 프라이빗 혼용 모드가 되어있긴 하다.
내가 의도한 플러그인들만 설치된 것이 확인된다.
간단하게 모니터링 에이전트 애드온을 추가해 업데이트한다.
이건 노드에 들어가서 노드를 describe한 건데, 이렇게 노드에 다양한 추가적인 정보를 제공해준다.
보안 그룹
보안 그룹은 내가 잘못 설정한 건지 여러 개가 만들어졌다.
vpc를 명시하지 않아도 알아서 만들어지게 하는 옵션이 있었을지도 모르겠다.
인스턴스에 연결된 보안 그룹은 eks라는 이름이 앞에 붙은 녀석들이었다.
각 보안 그룹 중에서 특이한 것은 노드 공유 보안그룹이다.
포트를 여러개 각각을 지정해서 열어주는 것이 보인다.
저 정도 포트 범위면 그냥 source만 고정하고 any로 하니 그러니
접속 후 확인
S-vpc 설정이 eks 액세스 엔드포인트에 미치는 영향에 트러블 슈팅 내용을 정리했다.
파게이트 프로필 만들고 테스트
fargate_profiles = {
default = {
name = "default"
selectors = [
{
namespace = "default"
labels = {
WorkerType = "fargate"
}
}
]
tags = {
Owner = "default"
}
timeouts = {
create = "20m"
delete = "20m"
}
}
}
이번에는 파게이트를 사용해보자.
프로필을 설정하고 라벨링만 잘해주면, 파드 배포 시 eks의 어드미션 컨트롤러가 쏙 요청을 가로채서 파게이트 스케줄러에게 일을 떠맡길 것이다.
예시가 많아서 대충 따왔다.[5]
파게이트 프로필은 무조건 프라이빗 서브넷을 사용할 것이 강제된다.
subnet_ids = module.vpc.private_subnets
그럼 넣어주지 뭐..
성공적으로 배포된 모습이다.
워크로드 배치
hostpath는 지원하지 않는 모양이다.
ephemeral 스토리지를 주긴 하나, 동적 프로비저닝은 제공하지 않는다고 했다.
어.. 의도했던 것은 아니나, 스케줄링 단계에서 fail되면 nomitated node도 안 뜨는 반면, 스케줄링 자체는 성공하고 난 이후 pending이 되는 상황에 대해서도 확인할 수 있었다.
이번 이슈는 로깅 컨피그맵이 없다는 것이다.
그렇다면 설정을 해줘야지.
여기에 자세한 예제가 나와있어서 어렵지 않을 듯하다.[6]
kind: ConfigMap
apiVersion: v1
metadata:
name: aws-logging
namespace: aws-observability
data:
flb_log_cw: "false" # Set to true to ship Fluent Bit process logs to CloudWatch.
filters.conf: |
[FILTER]
Name parser
Match *
Key_name log
Parser crio
[FILTER]
Name kubernetes
Match kube.*
Merge_Log On
Keep_Log Off
Buffer_Size 0
Kube_Meta_Cache_TTL 300s
output.conf: |
[OUTPUT]
Name cloudwatch_logs
Match kube.*
region region-code
log_group_name my-logs
log_stream_prefix from-fluent-bit-
log_retention_days 60
auto_create_group true
parsers.conf: |
[PARSER]
Name crio
Format Regex
Regex ^(?<time>[^ ]+) (?<stream>stdout|stderr) (?<logtag>P|F) (?<log>.*)$
Time_Key time
Time_Format %Y-%m-%dT%H:%M:%S.%L%z
일단 예제 컨피그 맵을 다운받아서 리전을 설정하고 배포한다.
네임스페이스도 당연히 만들어줘야 한다.
일단 이것만 되면 파드가 배치되기 시작한다.
근데 이것만으로도 해결되지 않는다.
이번에는 이미지를 풀 받을 권한이 없는 것이 문제인 것으로 보인다.
최근에 있었던 허브 이슈는 아닐 거라 생각한다.[7]
이미지를 받는데 계속 문제가 생긴다.
생각해보니까, 웬만한 설정을 퍼블릭 서브넷에 하다보니까 프라이빗 서브넷을 위한 nat 설정을 하는 것을 깜빡했다.
조금 검색하다보니 nat 게이트웨이 비용으로 인한 대안으로 nat 인스턴스를 만드는 사람도 있다.[8]
만드는 건 어렵제 않겠으나 어차피 ecr도 실습 예정이니 ecr에 엔드포인트를 뚫는 것을 연습해보자.
ecr 엔드포인트 연결
이것도 문서가 잘 나와있는 편이다.[9]
ECR은 저장소로서 S3를 사용하기에 s3도 사용해야 한다고 한다.
그래서 s3 게이트웨이 하나, ecr에 대한 인터페이스 2개를 만들었다.
available이 되는데 5분 가까이 걸린 것 같다.
보안그룹은.. 귀찮아서 다 때려박았다.
이러면 s3 게이트웨이가 하나 보인다.
이것만 설정하면 되려나?
ㅋㅋ 퍼블릭 레포는 또 허용 안하는 거였냐고.
그러면 프라이빗 레포 하나파서 해보자.
프라이빗 레포 파서 올리기
이건 오랜만이라 조금 정리해둔다.
aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin 134555352826.dkr.ecr.ap-northeast-2.amazonaws.com
이미 프라이빗 레포를 판 상황에서 진행하는 것이다.
일단 도커 빌드를 하던가 아무튼 이미지에 태그 달아서 레포에 올릴 호스트에서 로그인을 진행한다.
docker tag nginx:latest 134555352826.dkr.ecr.ap-northeast-2.amazonaws.com/zerotay/nginx:latest
docker push 134555352826.dkr.ecr.ap-northeast-2.amazonaws.com/zerotay/nginx:latest
어차피 내 로컬에 nginx 이미지가 있기 때문에 태그 하나만 새로 만들고 바로 올려준다.
조아쓰~
해당 이미지 주소를 이용해 파게이트 배치를 하니.. 드디어 된다..
대충 두어 시간 걸린 듯..
분석
생각해보니까 그냥 관찰하고 어떤 상황인지 확인만 하는 건데 분석이라 이름 붙이기 뭣한 것 같다..?
머리를 너무 많이 썼는데 당장 대체할 단어가 안 떠올라서 일단 패스..
eks 콘솔에서 파게이트 노드가 확인된다.
한 노드가 한 파드를 담당한다.
노드 이벤트는 한 파드밖에 없으니 파드 관련한 정보라고만 봐도 무방하겠다.
해당 파드가 받은 ip로 바로 통신을 하는 것이 가능하다.
물론 프라이빗 서브넷에 있기 때문에 현재 퍼블릭 서브넷에 있는 인스턴스에 접속한 상태에서 요청을 날렸다.
내 로컬에서 요청을 날리기 위해 로드밸런서 서비스를 만들어서 날려봤다.
클래식 로드밸런서가 하나 뚝딱 만들어진 게 확인된다.
보안 그룹도 하나 만들어졌는데, 모든 소스로부터 ping과 http가 가능하도록 설정됐다.
http는 내가 뚫어둔 포트 때문에 그런 것 같은데, ping을 이렇게 맘대로 뚫을 줄은 몰랐네.
어차피 파드가 icmp를 받을 준비가 안 돼 있기에 상관은 없을 것이다.
자원 삭제 이슈
신나게 실습하고, 지우려고 했더니 이런 에러가 생겼다.
eni가 없어지지 않는 이슈가 있었고, 이것도 보니까 권한이 없다고 한다..
근데 루트 계정으로 들어왔는데 권한이 없어버리면 어쩌라는 걸까?
삭제를 할 때 인터페이스 타입이 남아있다고하는데, 지우는데 오래 걸리긴 했지만 일단 그것도 다 지우긴 했는데 말이다.
비슷한 상황을 겪는 사람이 있던 모양이다.[10]
정답은 로드밸런서가 삭제되지 않았던 이슈였다.
급 드는 생각.
ecr 풀스루 캐시를 사용한다면, 처음에는 인터넷으로 나가서 이미지를 가져와야 한다.
프라이빗 서브넷에서 nat를 사용하는 것은 비용 부담이 크니 한 번만 nat를 타야한다면 그 순간에만 nat를 만들었다가 없애는 방식이 가능할까?
그걸 자동화해주는 것은 존재하나?
아니면 더 좋은 방법이 있나?
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": [
"logs:CreateLogStream",
"logs:CreateLogGroup",
"logs:DescribeLogStreams",
"logs:PutLogEvents",
"logs:PutRetentionPolicy"
],
"Resource": "*"
}]
}
그 후에는 이 정책을 만들고 파게이트 프로필에에 붙여주면 된다.
bottlerocket ami 쓰고 차이점 비교
private ecr 연동
관련 문서
이름 | noteType | created |
---|
참고
https://registry.terraform.io/modules/terraform-aws-modules/eks/aws/latest?tab=inputs ↩︎
https://github.com/terraform-aws-modules/terraform-aws-eks/issues/1910 ↩︎
https://github.com/terraform-aws-modules/terraform-aws-eks/blob/master/docs/faq.md ↩︎
https://github.com/terraform-aws-modules/terraform-aws-eks/blob/v17.24.0/outputs.tf ↩︎
https://github.com/terraform-aws-modules/terraform-aws-eks/blob/master/modules/fargate-profile/README.md ↩︎
https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/fargate-logging.html ↩︎
https://statusgator.com/services/docker?utm_source=chatgpt.com 이거 링크 탄 소스가 쿼리 스트링으로 붙는다? ↩︎
https://docs.aws.amazon.com/ko_kr/AmazonECR/latest/userguide/vpc-endpoints.html ↩︎
https://stackoverflow.com/questions/37232965/issue-when-trying-to-delete-vpc-and-network-interface ↩︎