반응형

Claude 가 비싸서 OpenClaw를 VPS 에 구성해보자...

 

1. VPS 구성 및 OpenClaw 설치

https://www.hostinger.com/

 

Hostinger - Bring Your Idea Online With a Website

Choose Hostinger and make the perfect site. From Shared Hosting and Domains to VPS and Cloud plans. We have all you need for online success.

www.hostinger.com

 

hostinger를 선택한 이유는 UI 를 제공하며, 가격도 나름 다른곳과 비교했을때 합리적

그리고 NVMe 라 Local LLM을 사용하게될 때 모델 로딩이 빠를것이라고... 생각했다

 

OpenClaw
Telegram Bot 구성

 

2. OpenClaw 설정

Gateaway Token 을 사용해서 Admin UI 접근

 

## qwen3:8b

curl -fsSL https://ollama.com/install.sh | sh
ollama pull qwen3:8b
ollama serve  # 백그라운드 실행은 systemctl

 

  qwen3:14b qwen3:8b gemma3:12b deepseek-r1:14b
MMLU 81.1% 74% 74.5% 76%
HumanEval ~80% 72% 71% 75%
GSM8K ~87% 82% 75% 86%
RAM 사용 9.3GB 5GB 7.8GB 9GB
응답 속도 보통 빠름 빠름 느림

 

 

좌측하단에 AI/Agent 클릭

 

ollama Provider 설정

 

openclaw v2026.3.28 부터 ollama 명시 설정이 깨진 버그가 있어서 OpenAI 호환 Adapter 를 사용해야 함

https://github.com/openclaw/openclaw/issues/57099

 

[Bug]: Explicit api ollama provider config fails with No API provider registered after upgrade to 2026.3.28 · Issue #57099 · o

Bug type Regression (worked before, now fails) Summary After upgrading from v2026.3.24 to v2026.3.28, any explicitly configured provider with api ollama fails immediately with: Error: No API provid...

github.com

 

3. Qwen 모델 설정

OpenClaw Container내 설정에서 Ollam 모델 추가 필요하다 

 

## .env 내 host ip 추가

echo "OLLAMA_HOST=http://76.13.187.254:11434" >> /docker/[openclaw-gateway path]/.env

## openclaw.json 모델 설정 추가
openclaw.json 내 ollama 모델 설정 추가

 

 

 

5. Issue 모음

 

ollama 지원을 안하기때문에 openai-completion 으로 진행 해야 함

 

 

권한문제 해결해야 함

operator.pairing 권한이 필요한데, 텔레그램 사용자가 아직 페어링 되지 않아서 발생

반응형
반응형

개요

  • Multi Cluster 관리방안
  • Addons 관리방안 (Helm, manifest)

 

EKS 를 구성하는 과정에서, Multi Cluster 기반으로 관리를 하다보니 어떻게 해야 효과적으로 관리를 할지... 고민을 많이 한다.

고민을 하던 끝에 GitOps Bridge 기반에 관리방안이 있었다.

 

이게 왜 필요한데?

  • Multi Cluster일 때 Load Balancer Controller에 설정값을 동일하게 적용해야 할 때 (예: IAM Role ARN, cluster name 등 클러스터마다 다른 값을 각각 관리해야 함)
  • 신규 Cluster 생성 시, 기존 사용하던 Addons들을 동일하게 설치해야 할 때
  • Terraform으로 만든 클라우드 리소스 정보(IAM ARN, VPC ID 등)를 ArgoCD/Helm이 알아야 할 때

 

GitOps Bridge

https://github.com/gitops-bridge-dev/gitops-bridge

 

GitHub - gitops-bridge-dev/gitops-bridge

Contribute to gitops-bridge-dev/gitops-bridge development by creating an account on GitHub.

github.com

 

GitOps Bridge 의 동작구성은 아래와 같다.

  • IaC <-> Gitops 연결을 통해 메타데이터 기반으로 ArgoCD 에서 활용이 가능
  • ApplicationSet 으로 여러 클러스터에 동일한 Addon 을 일괄 배포
  • Terraform을 사용한다면, 인프라프로비저닝 만 진행하고 그외것은 GitOps Engine으로 구동진행 (역할 분리)
  • 신규 클러스터 추가 시, Secret 만 등록하여 Addons 자동 설치

 

기본적인 Architecture 구성

동작원리는 아래와 같다.

  • EKS Cluster에 신규 구성될때 필요한 내용을 K8S Secret 으로 저장
  • Secret 에 저장된 내용을 기반으로 ArgoCD ApplicationSet generators 에 의해 여러 Application을 생성
  • 이때의 Application은 Addons 및 필요한 워크로드가 생성되는 패턴임

 

참고

 

 

 

반응형

'Architecture > K8S' 카테고리의 다른 글

EKS - Rolling Update  (0) 2026.01.24
k8s AutoScaling 동작구조 비교  (0) 2025.06.21
K8S - 시작 ( 용량산정 / 고려사항 )  (2) 2025.06.02
K8s - Karpenter  (2) 2025.06.01
반응형

개요

  • EKS Version Rolling Upgrade
  • Addons Update
  • Control Plane / Data Plan Update

요구사항

  • EKS 1.33 -> 1.34 (Control Plane Update)
  • 기본적으로 제공되는 Managed Addons Update
  • 설치한 Addons들 업데이트 
  • Data Plane Update (Rolling ...)
    • 이때 Data Plane은 Self hosted 로 테스트 진행

 

Self hosted Node Group 생성 

  • Managed NodeGroup으로 안한이유는... 회사환경과 맞추기위함
  • node group 생성
    • eksctl create nodegroup -f test-ng.yaml (System node) 
    • eksctl create nodegroup -f app-ng.yaml (App node)
# test-nodegroup.yaml
# eksctl create nodegroup -f test-ng.yaml
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
  name: hub
  region: ap-northeast-2

managedNodeGroups: []  # 기존 managed는 건드리지 않음

vpc:
  id: vpc-id
  securityGroup: sg-id
  subnets:
    private:
      ap-northeast-2a: { id: subnet-id }
      ap-northeast-2b: { id: subnet-id }
      ap-northeast-2c: { id: subnet-id }

nodeGroups:
  - name: test-upgrade-ng
    instanceType: t4g.small
    desiredCapacity: 2
    minSize: 1
    maxSize: 3
    privateNetworking: true
    amiFamily: AmazonLinux2023
    labels:
      role: test
      version: "1.33"
      node.kubernetes.io/role: "system"
      node-type: "system"
    tags:
      Environment: test
      k8s.io/cluster-autoscaler/hub: "owned"
      k8s.io/cluster-autoscaler/enabled: "true"
  • 2개의 Self hosted Node Group은 Cluster AutoScaler 로 구성
exteraArgs:
  scale-down-unneeded-time: 10m
  scale-down-utilization-threshold: 0.4

nodeSelector:
  role: test

 

 

1.33 -> 1.34 로 업그레이드 (Rolling Upgrade)

Core Addons 들의 Version 확인

aws eks describe-addon-versions \
  --kubernetes-version 1.34 \
  --addon-name coredns \
  --query 'addons[0].addonVersions[0].addonVersion' \
  --output text

aws eks describe-addon-versions \
  --kubernetes-version 1.34 \
  --addon-name kube-proxy \
  --query 'addons[0].addonVersions[0].addonVersion' \
  --output text

aws eks describe-addon-versions \
  --kubernetes-version 1.34 \
  --addon-name vpc-cni \
  --query 'addons[0].addonVersions[0].addonVersion' \
  --output text

 

자체적으로 설치한 Addons 들의 1.34 호환 버전을 찾아보자

  • 이건 직접 발품찾아야 함 -> 대부분 신규 버전이라면 호환됨...
  • external-dns
  • load-balacner-controller
  • cluster-autoscaler

 

Managed Node Group (Self hosted) 는 Instance refresh를 사용해서 Upgrade

  • 이때, 주의할건 업그레이드 시점에서 Cluster AutoScaler로 인해 ScaleIn 이 될수있기때문에 미리 Replica를 0으로 줄여놓자...
## https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/charts/cluster-autoscaler/values.yaml
replicaCount: 0

exteraArgs:
  scale-down-unneeded-time: 10m
  scale-down-utilization-threshold: 0.4

nodeSelector:
  role: test

 

 


 

좋아... Rolling Upgrade Let's go

 

0. Cluser AutoScaler 0 으로 줄이자... (위에 기재함)

1. Control Plane Upgrade (1.33 -> 1.34) + Core Addons Upgrade

  • Terraform 으로 1.34 로 올리면, 다른 Core Addons 들도 같이 올라가는것으로 보인다...
  • Control Plane은 10분가량 걸림 (terarform 설정을 봐야겠지만, addons들은 버전이 안바뀌었음 -> 수동으로 설정)

Update 중...

 

 

 

2. Managed Addons Upgrade (없으면 생략)

  • 이건생략 함

 

3. Data Plane Upgrade (Restart)

  • Launch Template 수정 (1.34 AMI 에 맞게)
## x86
aws ssm get-parameter \
  --name /aws/service/eks/optimized-ami/1.34/amazon-linux-2023/x86_64/standard/recommended/image_id \
  --region ap-northeast-2 \
  --query 'Parameter.Value' \
  --output text
  
## arm
aws ssm get-parameter \
  --name /aws/service/eks/optimized-ami/1.34/amazon-linux-2023/arm64/standard/recommended/image_id \
  --region ap-northeast-2 \
  --query 'Parameter.Value' \
  --output text
  
 ## LT 수정
 aws ec2 create-launch-template-version \
  --launch-template-id lt-xxxxx \
  --source-version '$Latest' \
  --launch-template-data "{\"ImageId\":\"$AMI_ID\"}" \
  --region ap-northeast-2

 

 

  • 기존 인스턴스의 용량을 2배로 증설하고, 추가된 인스턴서의 ScaleIn Protection 후 기존 Desired Count를 기존용량으로 줄인다
    • 이때 위험한 워크로드라면, 1개씩줄이자...

축소를 걸고 -> 다시 용량 줄이기...

 

  • 이슈가 없는지 잘 모니터링 하자...
kubectl get events --all-namespaces --watch --sort-by='.lastTimestamp'

 

FailedToUpdateEndpointSlices <- 이런 에러가 가끔 나지만, 노드 삭제중 일시적으로 발생하는 에러임 ( Pod 스케쥴링으로 자동 해결 됨)

반응형

'Architecture > K8S' 카테고리의 다른 글

1. GitOps Bridge Architecture [개념]  (0) 2026.05.10
k8s AutoScaling 동작구조 비교  (0) 2025.06.21
K8S - 시작 ( 용량산정 / 고려사항 )  (2) 2025.06.02
K8s - Karpenter  (2) 2025.06.01
반응형

개요

  • AutoScaling 동작구조 비교
  • Node Scaler
  • Pod Scaler


요즘 EKS를 공부하고 있는데 와... addons가 너무많다

진짜 그냥 addons 조합인 것 같다.

 

진짜 추후에는 K8S를 효과적으로 사용하려면 addons 버전도 리스팅해서 사용해야 할 판이다.

하여튼, AutoScaling 구조를 한번 파악해보자...

 

K8S 에서는 주로 Node / Pod 의 대한 스케일 정책이 존재한다. 그냥 간단히 정리하면...

  • Node === EC2
  • Pod === Container 

용량이 급증하면 한개의 Deployment는 여러개의 Replica ( Pod ) 를 가지게 되고 -> Pod Scaler

한 Node안에 Pod를 더이상 배치할 수 없다면? -> 노드를 증가시켜 증가된 노드에 파드를 배치한댜 -> Node Sclaer

 

그럼 뭐가 이렇게 많은건데...

 

Node Scaler

Cluster AutoScaler

CA는 의외로 간단하다.

 

api-server 간의 계속 적인 통신을 진행한다.

  • pod가 pending 상태가 아닌지?
  • node의 사용량이 적절한지?
  • nodegroup -> 각각의 pod가 어디 소속인지?

그 과정에서 이벤트가 발생하게 되면, ASG에게 요청을 한다

  • desired +1 
  • desired -1

ASG가 대신 노드 (EC2)를 스케일아웃 , 스케일인 진행을 해준다.

 

Scale in 진행시에는 어떻게 될까?

  • CA는 각 Node를 주기적으로 점검
  • 유후노드를 발견하면 다른 Pod들을 선정된 노드로 옮길 수 있는가 평가
  • 대신... 아래 Pod가 있다면 해당 노드는 제거 불가
    • DeamonSet
    • Larget Storage
    • Static Pod
    • Unreplicated Pod
    • PodDisruptionBudget 위반

결국, CA는 ASG를 통해서 AutoScaling을 한다.

그리고 ASG는 알다시피 느리다...

Karpenter

카펜터도 일단 api-server와의 통신을 계속 유지한다.

  • Pending 파드 감지
  • 요구 리소스 분석
  • 인스턴스 타입 결정 -> 가장 적절한 인스턴스 타입을 스스로 선택 (여기서 비용절감이 이뤄짐)
  • EC2 인스턴스 Launch
  • 노드 Join 감시
  • 유후 시 EC2 Terminate

그 이후에 작업은 ASG가 아닌 본인이 직접 AWS EC2 API를 활용해서 호출한다

AWS EC2 API 는 ASG보다 더 빠르게 동작한다.

 

어떤게 더 좋은가?

당연히, Karpenter

  • 적절한 인스턴스를 선택한다는 점에서 -> 비용 효율적
  • OnDemand 뿐만 아닌, SPOT 인스턴스로 고려한다는 점
  • AWS API를 사용한 방식으로 인해, ASG보다 몇배 빠르다는 점

어떻게 사용해야 할까? (이거 관련해서도 Poc 해봐야 할듯)

  • 스팟인스턴스를 우선 사용하되, 없으면 온디맨드를 사용하게 끔 구성 (어차피 AutoScaling 이라 안정되면 스케일인 될거기에)
  • 특정 워크로드만 GPU , ARM 인스턴스로 보내도록 제어가능
  • TTLSecondsAfterEmpty 활용해서 노드 자동 회수 기능
  • taints 와 labels을 사용해서 워크로드 분리

 

Pod Scaler

 

HPA (Horizontal) + metrics server 꼭 필요함

HPA는 굉장히 간단하다. CPU / Memory 또는 Custom 한 메트릭을 기반으로 파드의 개수를 조절한다

보통 Helm 으로 Deployment를 구성하면 쉽게 사용이 가능하다

 

  • HPA는 주기적으로 Metric API에서 리소스 사용률을 확인
  • TargetValue와 현재 리소스 사용률을 비교해서 적절한 Replica 수를 조정
  • Deployment에 Patch를 요청해서 Replica 수를 조정
  • Scheduler가 새로운 파드를 스케쥴링

스케일 계산공식은 아래와 같다

desiredReplicas = ceil[
  currentReplicas × ( currentMetricValue / targetMetricValue )
]

예를들면,

현재 3개의 파드가 존재하고
CPU 사용률 평균이 80%, 목표가 50% 라면

3 * ( 80 / 50 ) = 4.8 -> 5개로 늘리는건다.

그럼...

현재 Pod가 1개고
CPU 사용률 평균이 90%고, 목표가 40% 라면

3 * ( 90 / 40 ) = 6.75 -> 7개

 

VPA (Vertical) -> 개발초기 때나 한번 해보려나? / 배치작업?

VPA는 파드의 실제 리소스 사용량을 모니터링 하고, 

리소스 요청값과 제한값에 의해 동적으로 조정하는 컨트롤러 <- 이게 무슨 얘기야...

 

아... 그러니까 VPA는 기본적으로 Deployment에 기재된 request, limit 을 기준으로

데이터를 분석해서, 자동으로 request 와 limit을 조절해준다... (재배포)

 

약간, 아래와 같은 형태라면 VPA를 사용해봄직 하다

  • 아직 서비스의 알맞은 리소스 값을 모를 때 (cpu, mem)
  • 리소스 사용량이 점진적으로 변하는 서비스일때 (데이터 처리 배치작업 ...)
  • 수평확장이 안되는 경우

 

KEDA (Event Driven 가능)

사실 이걸 제일 공부해보고 싶다

외부 이벤트에 상태를 감지해서 파드 수를 동적으로 스케일링 하는 Auto Sclaer 

 

  • 기본적으로 Keda Operator 를 활용해서 다른 외부시스템과 연동한다
  • 이벤트 상태를 주기적으로 체크
  • 이벤트 수량이 임계값을 초과한다면 -> HPA Replica 수 조절을 요청한다

아... HPA랑 같이 사용하는거군요...

 

만약... 구성하게 된다면

  • NodeScaler 는 무조건 Karpetner
  • PodScaler는 모두 사용할 것 같음
    • 간단한 CPU / Memory 스케일링은 HPA를 활용
    • VPA를 사용해서 최적값을 찾아나가게끔 모니터링 진행 (updateMode : off)
    • SQS, Kafka 와 같은 메시지 큐 부하에 의해 자체적인 스케일링을 하기위해 KEDA 설정
반응형

'Architecture > K8S' 카테고리의 다른 글

1. GitOps Bridge Architecture [개념]  (0) 2026.05.10
EKS - Rolling Update  (0) 2026.01.24
K8S - 시작 ( 용량산정 / 고려사항 )  (2) 2025.06.02
K8s - Karpenter  (2) 2025.06.01
반응형

개요

  • 바람직한 개인 / 조직간의 IaC 구조

 

이제 Terraform, Terragrunt, CDK 같은 Tool도 손에 익었고...

Atlassian, Terraform Cloud, Dagger 같은 오픈소스도 사용하고 왜 필요한지도 알았고...

소규모 조직 ~ 어느정도 중규모 조직에서도 IaC를 구성해봤고...

 

어떻게 하면 IaC 라는 하나의 작업을 대규모 개발조직에서 효과적으로 할 수 있을까?

 

IaC를 잘못사용하고 있다는 증거

꽤 많은 소스코드와 ,여러사람들과 협업을 하다보니 편하려고 구성한 Iac가 오히려 독이 되는 케이스가 존재했다.

내가 생각했을때 아래의 대한 내용이 포함된다면 그런 것 같다. (굉장히 주관적)

 

  • Module 을 중앙집중화 해서 사용하는가? 
  • Module 이 계층으로 구성되어 있는가? 
  • 서비스를 만들때, Terraform Code가 얼마나 증가하는가? 
  • 태그 같은 공통 규칙을 한눈에 볼수 있는가? (IaC 안에서...)  - 구조의 문제
  • Terraform 프로젝트의 Folder Depth가 3~4 번 이상 진행되는가? - 구조의 문제
  • Devops 인원만 Terraoform을 사용하는가? 
  • ...

 

Module의 대한 이야기

모듈을 사용하는 이유는 뭐.. 너무 명확하다. 

전사내의 인프라 규칙자체를 중앙화 하는 목적이기도 하고, IaC 코드내에서도 어느정도 통일이 되게끔 하는 이유도 있다. (그 외에도 많음)

 

하지만 이 논리가 무너지는 순간이 있다. 이로인해 모든 모듈 구성이 망가진다.

  • 모듈을 사용하되, 다양한 변칙이 존재하는 경우
  • 모듈이 모듈을 감싸고 있는 경우 ( 상위모듈 = 하위모듈 + 하위모듈 )
  • 바쁜 경우 (이건 뭐.. 어쩔 수 없음)

 

예를 들어보자...

 

사실 하나의 모듈이 모든 의견을 수렴해서 짜는 건 당연하다

하지만 IaC를 잘 다루지 못하거나, Terraform Module 구성하는 것에 큰 경험이 없다면

무너지기 마련이다. -> 그냥 이순간 부터는 LB 관련된 모듈이 다양하게 쏟아진다. (재앙)

 

그럼 모듈은 어느 방식으로 구성하면 좋은가의 대한 얘기를 해보자...

보통 Back 개발을 하다보면 Layer를 주로 나누게 된다. Layer 구간에 각각의 목적성에 맞춘 코드를 작성한다

그것처럼 IaC도 결국 계층별로 Module을 정의하면 위 문제에서 나왔던 상위모듈 / 하위모듈 문제도 어느정도 해결 될 수 있다.

IaC Layer Architecture

모듈자체를 어느정도 계층을 두면 GraceFully 하게 해결될 수 있다.

Resource 라는 작은 모듈을 만들고 거기서 필요한 Set를 Service Layer로 구성하고,

Service 의 조합이 구성되면 하나의 Platform 모듈을 구성하게 된다면 -> 흔히 말하는 인프라를 찍어내는 형태로도 활용이 가능하다

 

위 구조형태로 구성하게 된다면, 문제로 제기되었던 아래문제도 어느정도 해결 될 수 있다.

  • 서비스를 만들때, Terraform Code가 얼마나 증가하는가?

어느 회사에서는 인프라 구축의 대한 요청이 들어오면, 

하나하나 만든다고 한다

  • ALB
  • ECS
  • AutoScaling Group
  • S3
  • Athena
  • Route53 Record
  • ...

그리고 다 하나씩 구성하면 apply 하면서 ... -> 정말 비효율적이다

 

회사내에서는 어느정도 인프라의 규격이 정해지기 마련이다.

해당 구조의 대한 Service Layer, Platform Layer에 모듈로만 정의해놓으면 "딸깍" 이면 끝날 수 도 있는 업무다.

 

 

Terraform을 사용하는 인원은 누구인가?

이건 회사마다 다를텐데... 거의 인프라를 담당하는 조직에서만 사용할 것 이다.

내가 생각했을때는 이건... 좋지 않다 왜 좋지 않냐? 

 

보통의 조직에서는 개발자 n 명 당, 인프라 인원 m 명을 배정해서 업무를 진행한다. (어딜가도 인프라 인원이 많은데는 본적이 없음)

  • 개발자 >>>>>> 인프라

그 과정에서 인프라 개발자들은 보통 아래 문제의 대해서 고민한다.

  • Compliacne 수립
  • 인프라 자동화 / 인프라 구성 일손 덜기
  • ...

그 과정에서 어느정도는 IaC 의 대한 일손을 개발자들에게 줘도 되지 않을까?

왜냐? 개발자들이 안바빠서? No

경험상, 위와같은 여러가지 상황이 많이 발생한다.

그럼... 간단하게 생각해 봤을때는?

 

  • 인프라를 모르는 사람은 -> Devops 가 짜주면 됨 -> 근데 바쁨 (다른 업무) -> 나중에 함
  • 인프라를 어느정도 아는사람은 -> Devops 가 구성 규칙만 알려주면 됨 -> 그럼 알아서 짬
  • 인프라를 남몰래 구축하는 사람은 -> 구축하는 Platform 이나 통로를 하나만 구성해주면 됨 -> 그럼 알아서 짬

 

자, 그럼 개발자분들이 어떻게 코드를 구성하게 할까? 간단한다

IaC 구조에서 Presentation Layer를 하나도 고려해본다면 그렇게 어렵진않다.

 

 

결국 하나의 통로만 만들어주면 된다.

AWS 의 Proton 이나 Catalog 와 같은 형태를 만들어주고,

그것이 json, yaml 로만 구성할 수 있다면 Terraform 에서는 jsondecode, yamldecode 메서드를 통해서 변수를 참조할 수 있다.

 

부작용

물론 위 구조가 정답일까? No

아마 반발이 많을 것 이다.

 

 

다양한 조직에는 다양한 사람이 있는 만큼 "왜 이걸 우리가 하냐" 라고 하는 사람도 있을 것이고,

"이럴거면 왜 Devops 가 필요하나" 여러가지 이유가 있을 수 있다.

 

또한 위 구조를 만드는 것 자체가 팀내에서는 큰 Challenge 가 될수도 있다.

 

결론...

저런 플랫폼을 만들어 보고싶음.. (희망) 

Devops 는 문화를 만드는 조직이니 만큼, IaC 라는 Tool에 국한되지 않고 더 큰 방법으로 활용하게 하고싶음

 

반응형
반응형

이제... 드디어 K8S 를 실무에서 사용해볼 수 있는 기회가 왔다.

이제 앞으로 나도 왜 K8S를 사용하는지 장점이 뭔지, 단점이 뭔지 더 디테일하게 토론할 수 있을것이다.

 

하지만, EKS를 구성하기전에 몇몇 고려사항등을 검토해봐야 한다.

 

용량 산정

집(용량) 구하기 Let'go

 

 

기본적으로 K8S는 Cluster 구조로 이루어져 있고, 1개의 Master Node와 여러개의 Worker Node로 이루어져 있다.

우리는 Pod라는 서비르를 Worker Node에 띄우지만, 이 구성을 어떤 방식으로 할 것이고 Node 의 용량을 어떻게 산정해야 할 것인가는 

중요한 문제이다.

 

용량을 산정하지 못하면, Pod는 Pending 상태로 계속 유지될 것이고 서비스는 정상적으로 동작하지 못할 것이다.

 

만약 Cluster 총 용량에 따라서 Node를 작게, 크게 나눈다라면...

클러스터 용량 작은 노드 여러개 큰 노드 여러개
8 CPU , 16 GB Memory - 2 core 4 GB Memory 
- 총 4 노드
- 8 core 16 GB Memory
- 총 1 노드
400 CPU, 800 GB Memory - 2 core 4 GB Memory
- 총 200 노드
- 8 core 16 GB Memory
- 총 50 노드

 

각각의 장단점은 뭘까?

  장점 단점
작은 노드 여러개 - 노드 장애 시, 클러스터 전체에서 피해가 적다 (노드 여러개니까...)
- HA 구성 시 유리
- 관리하는 노드가 많아 -> 마스터 노드 부하증가
- 공식적으로 5000개의 이하 권장 ( 500개정도여도 부하가 생김) -> Master Node 스펙업하면 됨
- 노드마다 기본적으로 사용되는 리소스 존재

리소스 스펙이 낮다면 (전체 리소스 대비 실제로 사용하는 서비스 리소스가 적을 수 있음)
- Karpenter...
- Keda...
- DeamonSet...
큰 노드 여러개 - 관리의 편의성
- 장비의 대한 업데이트 / 트러블슈팅이 적다
- 총 예산이 저렴하다 ( 10 > 1 )
- 큰 어플리케이션을 돌릴 때 유용
- 하나의 노드에 많은 pod가 실행된다 
- Kubelet, Container Runtime에 부하
- 노드 당, 최대 110개의 pod를 추천

- Rplicaiton 효율이 떨어짐 (HA 의미가 없음)
- 장애발생 시 위험함 (다른 노드에 부하가 클 수 있음)

 

결론적으로

 

큰 노드 여러개는

  • 관리가 편하고, 장비 및 업데이트의 대한 트러블 슈팅이 적고, 큰 어플리케이션을 돌릴때 유용하나...
  • 하나의 노드에 많은 Application이 동작하는 만큼 부하 및 서비스 장애 시, 다른 노드 즉 Cluster 전체의 대한 장애가 발생할 수 있다

작은 노드 여러개는

  • 장애발생 시, 클러스터 전체에 피해가 적을 수 있고 HA 구성할때 유리하다... -> 작은 서비스가 여러개라면 최적임
  • Node가 많아지는건 Master Node에 대해 부하가 발생할 수 있으며
  • 기본적으로 Node 마다 구성되는 리소스들이 존재한다 (기본 구성의 Spec을 미리 알아야 할듯하다)
    • Karpenter
    • Keda
    • CSI
    • Kube Proxy...
    • DeamonSet
  • 이러한 리소스들이 어느정도 잡아먹는데, Node 스펙이 낮다면 Application이 얼마 못들어감

 

좋아.. 그럼 모든걸 고려했다면 아래 사이트에 이동해서 

결과적으로 내가 만든 서비스가 어떤 K8S Node Type이 적당한지 계산을 해보자...

https://learnk8s.io/kubernetes-instance-calculator

 

Kubernetes instance calculator

Explore the best instance types for your Kubernetes cluster interactively.

learnk8s.io

 

 

고려사항

  • 스토리지 및 네트워크 연동
  • 쿠버네티스 업그레이드 및 패치
  • 모니터링 및 로깅환경 구축
  • 마스터노드에 대한 장애복구

 

클러스터 모니터링

클러스터 로깅

 

아티펙트 저장소

  • 기본적으로 Docker Hub나 외부 Helm 저장소를 사용하나,
  • 외부 저장소에 오류로 인해서 실제 Application에 문제가 발생할 여지가 존재하기에 -> 내부 저장소를 사용하자

CI/CD 파이프라인

반응형

'Architecture > K8S' 카테고리의 다른 글

1. GitOps Bridge Architecture [개념]  (0) 2026.05.10
EKS - Rolling Update  (0) 2026.01.24
k8s AutoScaling 동작구조 비교  (0) 2025.06.21
K8s - Karpenter  (2) 2025.06.01
반응형

개요

  • K8S AutoScaling
  • AutoScaler vs Karpenter
  • Karpenter 동작원리
  • Karpenter Terraform

K8S AutoScaling

K8S 는 기본적으로 Node / Pod 기반으로 서비스가 동작한다

여기서의 Node는 EC2, Pod 는 Container로 해당된다.

 

이때, Node 즉 EC2의 경우 서비스가 많아짐에 따라 EC2 AutoScaling을 진행하게 된다.

  1. Pod AutoScaling 구성 
  2. Worker Node에 Pod가 많아짐 
  3. 기존 Node 용량보다 Pod가 많아질 경우, EC2 Node 가 더 생김

그럼 그냥 EC2 AutoScaling 을 Default 하게 사용하면 안되나? 

그렇게 써도 되긴하는데, 좀더 효율적인 방법의 AutoScaling 방법을 사용해야 한다.

 

EC2 Launch Template vs AutoScaler vs Karpenter

 

EC2 Launch Template + ASG

EC2 Launch Template + ASG

가장 기본적인 방식이다.

EC2 인스턴스 그룹을 미리 정의해두고, 트래픽에 따라 인스턴스를 수동 / 자동으로 확장할 수 있다.

Kubernetest 의 Pod 상태를 직접적으로 알기 어렵기 때문에 과잉 할당으로 인한 불필요한 비용이 발생할 여지가 존재함

운영자가 직접 Scale 정책을 설계해야 하기때문에, 유지관리 비용이 크다.

AutoScaler

AutoScaler

K8S 에서 주로 사용하는 AutoScaling 컴포넌트

Pod가 Node에 스케쥴링되지 못할 때 -> 필요에 따라 EC2를 자동으로 생성한다.

ASG완 연동되어 작동하며, 기본적으로 NodeGroup 단위로 동작한다.

다만, 아래와 같은 단점이 존재한다.

  • 각 NodeGroup의 대한 설정이 필요
  • 리소스 낭비가 발생하기 쉽다.
  • 스케일 속도가 느리다 ( AutoScaling 자체가 기본적으로 느림 )

Karpenter (밑에 사진 참조)

AWS 가 만든 오픈소스 Kubernetes AutoScaler (참 잘만듬)

Pod 스케쥴링 요청에 따라, 가장 적합한 인스턴스 타입과 크기를 실시간으로 계산하여 자동 생성한다

NodeGroup이 아닌, 단일 프로비저닝 정책만으로도 다양한 EC2 타입을 활용할 수 있다. (SPOT도 가능)

프로비저닝 속도가 빠르다 (awscli 사용해서 EC2를 생성하기 때문에 AutoScaling 보다 빠르다)

 

그냥 결론적으로 보면... (주관적)

  • 빠른가? -> Karpenter >>>>>> Launch Template == AutoScaler
  • 비용최적화로 EC2를 설정하는가? -> Karpenter 
  • 설정이 어렵나? -> Karpenter == Launch Template == AutoScaler

 

Karpenter 동작원리

 

pod 생성 후 -> node attach

 

Academic 한 이론으로는 아래와 같다.

  1. Pending Pod를 감지한다 -> Kubernetes에 Scheduling Cycle 중 스케쥴되지 못한 Pod를 감지한다
  2. Pod의 요구 리소스를 분석한다 (이 Pod를 수용할 수 있는 최적의 인스턴스를 구하는 단계)
    1. CPU / Memory / GPU 리소스 요구량
    2. NodeSelector, Trolerations, Affinity 제약 조건
    3. Storage / Zone / OS 특수 요구사항
  3. 적합한 인스턴스 타입 탐색
    1. 리전 및 가용역역
    2. on-Demand or SPOT 인스턴스 여부
  4. 인스턴스 프로비저닝
    1. 위 단계를 지나치고 선택된 EC2 타입을 직접 API로 생성 (아주 빠름)
      1. AMI
      2. Kubernetes Kubelet
      3. IAM Role, SG, Subnet 설정
  5. Pod 스케쥴링 및 Node 연결
    1. 생성된 EC2 인스턴스를 K8S Node로 Join
    2. Pending Pod를 해당 Node에 스케쥴링
  6. 유후 Node 감지 및 제거
    1. consolidation, ttlSecondsAfterEmpty, expiration 등의 설정으로 유휴 노드 제거 전략 조절 가능

 

하지만 난, 저걸 이해못함 -> 내가 이해한 바로는 ...

처음 카펜터를 구성하면 4가지? 를 생성 및 참조하게된다.

  1. 태그 참조
  2. Karpenter IRSA
  3. Karpenter Controller
  4. Karpenter Provisioner

태그참조

카펜터는 참 신기한 놈이다.

카펜터를 사용하겠다를 태그를 활용해서 구성한다.

  • 카펜터가 구성되는 EC2
  • 카펜터를 사용해서 Provisioning 되는 EC2의 SG
  • 카펜터를 사용해서 Provisioning 되는 EC2가 속한 Subnet 

에 올바른 Tag가 기입이 되어야 한다.

설정마다 다르겠지만, 나는 Blueprint

 

Karpenter IRSA

k8s에 장점이자 단점인, Service Account이다

즉, pod의 권한을 IAM Role로서 제한을 할 수 있다.

 

Node안에는 다양한 서비스의 Pod들이 떠있다. 그렇기 때문에 IRSA를 사용해서

namespace 별로, 각각 다른 serviceAccount (SA) 를 만들고 해당 Pod에 각각 다른 IAM Role을 부여해서 사용한다.

 

Karpenter Controller (언제 노드를 만들까?)

  • Deployment
  • 실제로 노드를 프로비저닝하는 실행 컴포넌트
  • 기본적으로 1개의 replica로 실행된다 (namespace = karpenter)

Karpenter Provisioner (어떤 노드를 만들까?)

  • Custom Resource (CR)
  • 노드 프로비저닝의 정책을 정의하는 설정 컴포넌트 <- 여기에 적힌 구성대로 Karpenter가 최적의 EC2를 찾아줌

 

Karpenter Terraform

variable "eks_attr" {
  default = {
    "name" : "donggyu-eks"
    "version" : "1.32"
  }
}

module "eks" {
  source  = "terraform-aws-modules/eks/aws"
  version = "~> 20.31"

  cluster_name                    = lookup(var.eks_attr, "name")
  cluster_version                 = lookup(var.eks_attr, "version")
  cluster_endpoint_public_access  = true // 외부에서 접근 가능
  cluster_endpoint_private_access = true // 내부에서 접근 가능

  create_cluster_security_group = false
  create_node_security_group    = false

  # Optional: Adds the current caller identity as an administrator via cluster access entry
  enable_cluster_creator_admin_permissions = true

  vpc_id     = local.vpc.vpc_id
  subnet_ids = values(local.vpc.was_subnets)

  # EKS Addons
  cluster_addons = {
    coredns = {
      most_recent = true
    }
    kube-proxy = {
      most_recent = true
    }
    vpc-cni = {
      most_recent = true
    }
    aws-ebs-csi-driver = {
      most_recent = true
    }
    # eks-pod-identity-agent = {}
  }

  eks_managed_node_groups = {
    karpenter = {
      instance_types = ["t4g.medium"]
      ami_type = "AL2_ARM_64"
      min_size = 2
      max_size = 5
      desired_size = 2
      # capacity_type = "SPOT"

      iam_role_additional_policies = {
        "AmazonSSMManagedInstanceCore" = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
      }
    }
  }

  node_security_group_additional_rules = {
    ingress_karpenter_webhook_tcp = {
      description                   = "Control plane invoke Karpenter webhook"
      protocol                      = "tcp"
      from_port                     = 8443
      to_port                       = 8443
      type                          = "ingress"
      source_cluster_security_group = true ## 해당 규칙의 소스를 cluster가 가지고있는 보안그룹으로 지정 (node간 통신허용)
    }
  }


  tags = merge({
    Blueprint = lookup(var.eks_attr, "name")
    }, {
    "karpenter.sh/discovery" = lookup(var.eks_attr, "name")
  })
}

################################################################
######## karpenter
################################################################
resource "aws_iam_instance_profile" "karpenter" {
    name = "KarpenterNodeInstanceProfile-${module.eks.cluster_name}"
    role = module.eks.eks_managed_node_groups["karpenter"].iam_role_name
}



################################################################
######## aws-auth
################################################################
module "eks_aws_auth" {
  source  = "terraform-aws-modules/eks/aws//modules/aws-auth"
  version = "~> 20.0"

  manage_aws_auth_configmap = true

  aws_auth_roles = [
    {
      rolearn  = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/AWSReservedSSO_AdministratorAccess_18f0ecd34fab4ea6"
      username = "admin"
      groups   = ["system:masters"]
    },
    {
      rolearn = module.eks.eks_managed_node_groups["karpenter"].iam_role_arn
      username = "system:node:{{EC2PrivateDNSName}}"
      groups   = ["system:nodes", "system:bootstrappers"]
    }
  ]

  aws_auth_users = [
    {
      userarn  = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:user/admin"
      username = "admin"
      groups   = ["system:masters"]
    }
  ]
}
## karpenter pod가 AWS에 접글할 수 있도록 IRSA를 생성
module "karpenter_irsa" {
  source  = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"
  version = "~> 4.21.1"

  role_name                          = "karpenter-controller-${module.eks.cluster_name}"
  attach_karpenter_controller_policy = true

  karpenter_controller_cluster_id = module.eks.cluster_arn
  karpenter_controller_node_iam_role_arns = [
    module.eks.eks_managed_node_groups["karpenter"].iam_role_arn
  ]

  karpenter_controller_ssm_parameter_arns = [
    "arn:aws:ssm:*:*:parameter/aws/service/eks/optimized-ami/*"
  ]

  oidc_providers = {
    ex = {
      provider_arn               = module.eks.oidc_provider_arn
      namespace_service_accounts = ["karpenter:karpenter"]
    }
  }
}

# EKS 클러스터 접근 권한을 위한 IAM 정책
resource "aws_iam_policy" "karpenter_eks_access" {
  name        = "karpenter-eks-access-${module.eks.cluster_name}"
  description = "Policy for Karpenter to access EKS cluster"

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Action = [
          "eks:DescribeCluster",
          "eks:ListClusters",
        ]
        Resource = module.eks.cluster_arn
      },
      {
        Effect = "Allow",
        "Action" : [
            "ec2:RunInstances",
            "ec2:TerminateInstances",
            "ec2:DescribeSpotPriceHistory",
            "pricing:GetProducts"
        ],
        "Resource" : "*"
      }
    ]
  })
}

# IAM 정책을 Karpenter IRSA 역할에 연결
resource "aws_iam_role_policy_attachment" "karpenter_eks_access" {
  role       = module.karpenter_irsa.iam_role_name
  policy_arn = aws_iam_policy.karpenter_eks_access.arn
}

# karpenter_helm.tf

resource "helm_release" "karpenter" {
  name       = "karpenter"
  repository = "oci://public.ecr.aws/karpenter"
  chart      = "karpenter"
  version    = "v0.27.0"
  namespace  = "karpenter"
  create_namespace = true

  set {
    name  = "serviceAccount.annotations.eks\\.amazonaws\\.com/role-arn"
    value = module.karpenter_irsa.iam_role_arn
  }

  set {
    name  = "settings.aws.clusterName"
    value = module.eks.cluster_name
  }

  set {
    name  = "settings.aws.defaultInstanceProfile"
    value = "KarpenterNodeInstanceProfile-${module.eks.cluster_name}"
  }

  set {
    name  = "settings.aws.interruptionQueueName"
    value = module.eks.cluster_name
  }

  wait = true
}

# karpenter_manifest.tf
resource "kubectl_manifest" "karpenter_node_template" {
  yaml_body = file("${path.module}/karpenter.yaml")

  depends_on = [helm_release.karpenter]
}
apiVersion: karpenter.sh/v1alpha5
kind: Provisioner
metadata:
  name: provisioner
spec:
  requirements:
    - key: "node.kubernetes.io/instance-type"
      operator: In
      values: [ "t4g.medium" ]
    - key: "topology.kubernetes.io/zone"
      operator: In
      values: [ "ap-northeast-2a", "ap-northeast-2b", "ap-northeast-2c" ]
    - key: "eks.amazonaws.com/capacityType"
      operator: In
      values: [ "ON_DEMAND" ]
    - key: "kubernetes.io/arch"
      operator: In
      values: [ "arm64" ]
  
  # 생성할 인스턴스의 최대 리소스
  limits:
    resources:
      cpu: "10"
      memory: 20Gi
      # nvidia.com/gpu: 16
  
  # karpenter 프로비저닝 메커니즘 사용
  # ttlSecondsAfterEmpty 와 consolidation 은 동시에 사용 불가
  # consolidation:
  #   enabled: true
  #   ttlSecondsUntilExpired: 2592000 # 30 Days = 60 * 60 * 24 * 30 Seconds;      
  ttlSecondsAfterEmpty: 30

  # 생성된 인스턴스(worker node)에 지정되는 label
  labels:
    environment: donggyu-eks
    managed-by: karpenter
    Blueprint: donggyu-eks

  # 생성된 인스턴스(worker node)에 지정되는 taints
  # taints:
  #   - key: nvidia.com/gpu
  #     value: "true"
  #     effect: NoSchedule

  provider:
    # 생성한 인스턴스에 어느 보안 그룹을 적용할 것인지 보안 그룹의 태그로 지정
    securityGroupSelector:
      Blueprint: donggyu-eks
    
    # 어느 서브넷에 인스턴스를 생성할 것인지 태그로 지정
    subnetSelector:
      environment: donggyu-eks
      managed-by: karpenter
    
    # 생성된 인스턴스의 태그를 지정  
    tags:
      Blueprint: donggyu-eks

 

 

반응형

'Architecture > K8S' 카테고리의 다른 글

1. GitOps Bridge Architecture [개념]  (0) 2026.05.10
EKS - Rolling Update  (0) 2026.01.24
k8s AutoScaling 동작구조 비교  (0) 2025.06.21
K8S - 시작 ( 용량산정 / 고려사항 )  (2) 2025.06.02
반응형

개요

  • ECS 이전할때 Trouble Shooting...

 

ECS 이전할때 Trouble Shooting...

3주정도 ECS 이전하면서 

고생을 너무 많이 했다. 뒤돌아보면 별거 아닌 이슈들도 많았지만...

NEXT ( SSR ) 을 어느정도 공부할 수 있었던 계기가 된것같다. 

 

하지만 Front 는 여전히 너무 어려운것 같다...

 

1. Dockerizing 

Vercel 이라는 플랫폼이 동작하는 원리를 보면 아래와 같다.

  1. 브랜치 배포
  2. 배포된 브랜치 checkout 후, 그대로 build ( npm run build )
  3. build 된 산출물 기반으로 서버 시작 ( npm run start )

약간 과장된 부분이 없지 않아 있지만, 그냥 EC2에 올려서 쓰는느낌이다.

그래서 처음부터 Dockerzing 을 진행해야 했다.

 

여기서 살짝 정신이 1차로 나간것 같다.

// monorepo
################################################ Base 
FROM node:20-bullseye-slim AS base
## add canvas lib
RUN apt-get update && apt-get install -y \
    tree \ 
    python3 \
    make \
    g++ \
    build-essential \
    libcairo2-dev \
    libjpeg-dev \
    libpango1.0-dev \
    libgif-dev \
    librsvg2-dev \
    && rm -rf /var/lib/apt/lists/*
RUN npm i -g pnpm
################################################ Install 
FROM base AS builder
WORKDIR /usr/src/app

COPY package*.json pnpm-lock.yaml pnpm-workspace.yaml turbo.json .npmrc  ./
COPY apps/refund-web ./apps/refund-web

## 현재 작업하는 폴더 내 .env를 위치시켜야 함
## build 시 .env 가 알아서 .next/standalone으로 위치함
COPY .env ./apps/refund-web/.env
COPY packages ./packages
RUN pnpm install
ENV NEXT_PUBLIC_CDN_BASE_URL=[CDN 주소]
ENV NEXT_PUBLIC_APP_NAME=[APP NAME]
ENV NEXT_PUBLIC_RESOURCE_CENTER_URL=[CDN 주소]
ENV NEXT_PUBLIC_ZENV=[environment]
ENV NODE_ENV=[environemnt]

RUN pnpm run build:refund
RUN pnpm prune --prod
################################################ Runner
FROM node:20-bullseye-slim AS runner

COPY --from=builder /usr/src/app/apps/refund-web/.next/standalone/ .
COPY --from=builder /usr/src/app/apps/refund-web/.next/static ./.next/static
COPY --from=builder /usr/src/app/apps/refund-web/public ./apps/refund-web/public
EXPOSE 3000
ENV HOSTNAME=0.0.0.0

 

// single repo
FROM node:20.18.3-bullseye-slim AS base
RUN apt-get update && apt-get install -y \
    tree \
    python3 \
    make \
    g++ \
    && rm -rf /var/lib/apt/lists/*
WORKDIR /usr/src/app
COPY package*.json pnpm-lock.yaml ./
RUN npm i -g pnpm
# RUN pnpm install --frozen-lockfile --production
RUN pnpm install
RUN pnpm add sharp
COPY . .

##################### Requrie Changes... #####################
ENV CDN_BASE_URL=[CDN]
ENV NEXT_PUBLIC_SERVER_ZENT_API_URL=[API]
ENV APP_NAME=[APP_NAME]
ENV PORT=[PORT]
ENV NODE_ENV=[ENVIRONEMNT]
RUN pnpm run build
RUN rm -rf ./.next/cache

FROM node:20.18.3-bullseye-slim AS runner
WORKDIR /usr/src/app
COPY --from=base /usr/src/app/public ./public
COPY --from=base /usr/src/app/package*.json .
COPY --from=base /usr/src/app/.next/standalone ./
COPY --from=base /usr/src/app/next.config.js ./
COPY --from=base /usr/src/app/.next/static ./.next/static 
EXPOSE 3000
ENV HOSTNAME=0.0.0.0

 

자... Dockerfile 작성할때 유의할점도 같이 보자..

 

2. env 파일 생성

Backend 서버에서는 env 파일을 따로 두진 않고, ECS TaskDefinition에서 값을 넣어준다.

그렇게 해서 자체적으로 mapping 되게 하지만 NEXT는 달랐다.

 

애초에 Docker build 타임에 env 파일이 필요했다.

그래서 DockerBuild 하는 시점 전에, CD 쪽에서 .env 값을 만들어 줘야 했다.

COPY .env ./apps/refund-web/.env

 

3.  hostname 이슈 ( Next 13.4.13)

이건 특정 버전 이슈였는데, Next 13.4.13 버전에서 내부적으로 API를 호출하는데,

Docker환경에서만 비정상적인 내부포트를 자동으로 생성

그래서 해당 버전은 13.4.19 버전으로 수정했었다.

 

추후 이 문제로 많은 논의를 진행했는데 NEXT 자체가 버전문제가 많아서

최대한 15버전으로 옮기겠단 말을 해주셔따 (FE 감사)

 

 

4. next.config.js

NEXT 에서 Dockerzing 시, output 되는 dist 폴더 사이즈를 줄이기 위해 옵션을 추가해야한다. (output)

또한 cs / jss 접근 시, CDN 으로 접근하기 위해서도 아래 옵션을 기재해야 한다. (assetPrefix)

// next.config.ts
const { version: packageVersion } = require("./package.json");
/** @type {import('next').NextConfig} */
const zentNextConfig = {
  ...nextConfig,
  output: 'standalone', ## 이 옵션 필요
  assetPrefix: `${process.env.CDN_BASE_URL}/${process.env.APP_NAME}/${packageVersion}`, ## 이 옵션 필요 //.. options
  compiler: {
    //...nextConfig.compiler,
    removeConsole: {
      exclude: ["error"],
    },
  },
};
/** @type {import('next').NextConfig} */

 

5. health check route 추가

ECS로 옮기게 되면, Target Group이 Health Check를 통해서 api에게 질의를 한다.

이때 healthCheck 할 수 있는 router를 추가해야한다. (버전마다 상이)

  • next 12 
    • pages/api/ping.ts
  • next 13 ~ 15
    • src/app/ping/route.ts

6. hostname 이슈

몇몇 콘솔은 Cognito 를 사용하여 사용자 인증을 진행하였다.

이때, ALB -> ECS -> Cognito -> OKTA 형태로 동작하는데 아래와 같은 이슈가 발생하였다.

 

위 화면은 OKTA 인증 후 나타나면 화면이다.

.Har (관리자 콘솔 -> 네트워크 탭) 파일을 확인해도 별다른 내용을 확인 할 수 없었음 ...

 

ALB 내 Cognito 연결을 구성하는 부분이 있지만 -> 우리는 CallBack을 기반으로 동작해서 해당 문제는 아니었음

결국 Docker 내 hostname 이슈 였음 (아래와 같이 해결)

...
CMD HOSTNAME="https://[domain]" node ./apps/op/server.js

 

OKTA 에서 Service로 Redirect 해줄때, 내부 ip로 전달을 해주는 (hostname) 이슈였다. 

 

7. CD 구성... *****

#!/bin/bash

## if occured error > process.exit(1)
set -e 

# Variables
SERVICE_NAME=$1
ENV=$2
TAG=$3
S3_BUCKET=$4
DOCKERFILE_PAHT=$5
ASSET_PREFIX=$6

_ENV=$ENV

if [ "$ENV" == "prd" ]; then 
    echo "Deploy to production"
    ACCOUNT_ID=[account_id]
    ECS_CLUSTER_NAME=[prd-cluster]
    PORT="3000"
    CPU=2048
    MEM=4096
else 
    echo "Deploy to development"
    ACCOUNT_ID=[account_id]
    ECS_CLUSTER_NAME=[dev-cluster]
    PORT="3000"
    CPU=1024
    MEM=2048
fi

## 예외) env-1 ~ n
if [[ "$ENV" =~ ^([a-zA-Z]+)-([0-9]+)$ ]]; then
    ENV="${BASH_REMATCH[2]}-${BASH_REMATCH[1]}"
    ECR_REPOSITORY=dkr.ecr.ap-northeast-2.amazonaws.com/${SERVICE_NAME}-${BASH_REMATCH[2]}/${BASH_REMATCH[1]}
else
    ECR_REPOSITORY=dkr.ecr.ap-northeast-2.amazonaws.com/${SERVICE_NAME}/${ENV}
fi

echo $TAG
echo $ENV
echo $ECR_REPOSITORY

TASK_VERSION=v3.0.0                                    ## Task Definition Version
############################## Not Changed ##############################
TEAM=client
ECS_SERVICE_NAME=${SERVICE_NAME}-${ENV}-svc

ssm v4 --json ./devops-repo/task_definition/${TASK_VERSION}/task_def.json \
    --zent_account_id ${ACCOUNT_ID} \
    --zent_image_arn ${ECR_REPOSITORY}:${TAG} \
    --zent_port ${PORT} \
    --zent_service_name ${SERVICE_NAME} \
    --zent_env ${ENV} \
    --zent_dd_team ${TEAM} \
    --zent_cpu ${CPU} \
    --zent_mem ${MEM} \
    --zent_tag ${TAG}

cat task_def.json | jq
mv task_def.json ${DOCKERFILE_PAHT}/

aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin ${ACCOUNT_ID}.dkr.ecr.ap-northeast-2.amazonaws.com

cd ${DOCKERFILE_PAHT}
docker build -t ${ECS_SERVICE_NAME} -f Dockerfile --cache-from ${ACCOUNT_ID}.${ECR_REPOSITORY}:latest ../../
docker tag ${ECS_SERVICE_NAME} ${ACCOUNT_ID}.${ECR_REPOSITORY}:latest
docker tag ${ECS_SERVICE_NAME} ${ACCOUNT_ID}.${ECR_REPOSITORY}:${TAG}

docker push ${ACCOUNT_ID}.${ECR_REPOSITORY}:latest
docker push ${ACCOUNT_ID}.${ECR_REPOSITORY}:${TAG}

container_id=$(docker create ${ACCOUNT_ID}.${ECR_REPOSITORY}:${TAG})
docker cp $container_id:/.next .
docker rm $container_id

aws s3 sync .next/static s3://${S3_BUCKET}/[path]/${ASSET_PREFIX}/_next/static --cache-control max-age=31536000,public,immutable

TASK_DEFINITION_ARN=$(aws ecs register-task-definition \
    --cli-input-json file://task_def.json \
    --query 'taskDefinition.taskDefinitionArn' \
    --output text)

echo "Registered Task Definition: $TASK_DEFINITION_ARN"
          
aws ecs update-service \
    --cluster $ECS_CLUSTER_NAME \
    --service $ECS_SERVICE_NAME \
    --task-definition $TASK_DEFINITION_ARN \
    --force-new-deployment

echo "ECS service $ECS_SERVICE_NAME updated successfully with task definition $TASK_DEFINITION_ARN"

 

여기서 중요한 건,

아래 부분이다.

container_id=$(docker create ${ACCOUNT_ID}.${ECR_REPOSITORY}:${TAG})
docker cp $container_id:/.next .
docker rm $container_id

 

이게 무슨 얘기냐면, Dockerizng을 진행하게 되면 next.config.js 에 기재된 형태처럼

standalone 파일과 .next 폴더가 나오게 된다.

 

이때, standalone 폴더는 ECS 내 올라가야 하고

.next 폴더는 ECS가 아닌 S3 버켓에 올라가서 CDN으로 동작이 되어야 한다.

 

그렇기때문에 Docker Container 내에서 이 폴더를 로컬로 Copy 하여 S3 에 옮기는 작업을 해줘야 한다.

 

 

반응형

+ Recent posts