k8s 직접 해보기 (10) - EKS에 서버 배포하기

Endermaru·2025년 6월 26일

k8s 직접 해보기

목록 보기
11/11
  • 지금까지의 과정을 통해 k8s 및 관련 서비스들의 개념, k8s의 작동방법, 다른 팀의 서버 배포 등을 살펴볼 수 있었고, 마침내 우리 팀의 서버를 배포하는 단계가 되었다.
  • 물론 한 번의 PR로 깔끔하게 배포되진 못 했고, 여러 번의 사고와 수정을 거치며 배포가 진행되었다.
  • 여기서는 전체 완성된 버전을 먼저 본 뒤, 각 PR 별로 무엇이 부족했고, 무엇을 해결하려고 했는지 살펴보고자 한다.

완성본(dev서버)

apps/templates/internhasha-dev.yaml

  • ArgoCD의 Child App으로서 internhasha-dev 서버 어플리케이션을 정의한다.
  • waffle-world/apps/values.yaml에 정의되어 있는 source, destination, syncPolicy를 사용하여 EKS 클러스터에 배포된다.
    • Application 리소스는 argocd 네임스페이스에, 해당 애플리케이션이 관리하는 k8s 리소스(Deployment, Service, ...) 등은 internhasha-dev에 배포된다.
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  namespace: argocd
  name: internhasha-dev
  finalizers:
  - resources-finalizer.argocd.argoproj.io
spec:
  project: default
  source:
    repoURL: {{ .Values.spec.source.repoURL }}  # https://github.com/wafflestudio/waffle-world.git
    targetRevision: HEAD
    path: apps/internhasha-dev/internhasha
  destination:
    server: {{ .Values.spec.destination.server }}  # https://kubernetes.default.svc
    namespace: argocd
  syncPolicy:
    {{- .Values.spec.syncPolicy | toYaml | nindent 4 }}

apps/internhasha-dev/internhasha/internhasha-server.yaml

  • 대부분 저번에 살펴본 다른 서버의 manifest 파일과 내용이 비슷하다.
  • 모든 리소스는 internhasha-dev 네임스페이스에서 배포된다.
  • 2개의 환경변수를 env로 직접 설정하였다.
    • JVM 환경변수: JVM의 힙(메모리)를 컨테이너에 할당된 메모리의 80%로 제한
    • Spring profile을 dev로 설정
  • internhasha라는 이름의 ServiceAccount를 정의하여 Deployment에서 사용하였다.
  • istio 리소스에서 dev-api-internhasha.wafflestudio.com 호스트로부터 오는 트래픽은 internhasha-server 서비스 리소스로 라우팅하도록 규칙 설정
apiVersion: apps/v1
kind: Deployment
metadata:
  name: internhasha-server
  labels:
    app: internhasha-server
  namespace: internhasha-dev
spec:
  replicas: 1
  selector:
    matchLabels:
      app: internhasha-server
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
  revisionHistoryLimit: 4
  template:
    metadata:
      labels:
        app: internhasha-server
    spec:
      serviceAccountName: internhasha
      nodeSelector:
        phase: dev
      tolerations:
        - effect: NoSchedule
          key: phase
          operator: Equal
          value: dev
      containers:
        - image: 405906814034.dkr.ecr.ap-northeast-2.amazonaws.com/internhasha-dev/internhasha-server:12
          name: internhasha-server
          env:
            - name: JAVA_OPTS
              value: "-XX:InitialRAMPercentage=80.0 -XX:MaxRAMPercentage=80.0"
            - name: SPRING_PROFILES_ACTIVE
              value: "dev"
          resources:
            requests:
              cpu: 100m
              memory: 512Mi
            limits:
              cpu: 200m
              memory: 512Mi
          ports:
            - containerPort: 8080
          startupProbe:
            httpGet:
              path: /actuator/health/startup
              port: 8080
            initialDelaySeconds: 10
            failureThreshold: 20
          livenessProbe:
            httpGet:
              path: /actuator/health/liveness
              port: 8080
          readinessProbe:
            httpGet:
              path: /actuator/health/readiness
              port: 8080
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: internhasha
  namespace: internhasha-dev
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::405906814034:role/internhasha-dev-role
---
apiVersion: v1
kind: Service
metadata:
  namespace: internhasha-dev
  name: internhasha-server
spec:
  type: ClusterIP
  selector:
    app: internhasha-server
  ports:
    - port: 80
      targetPort: 8080
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  namespace: internhasha-dev
  name: internhasha-server
spec:
  gateways:
    - istio-ingress/waffle-ingressgateway
    - mesh
  hosts:
    - dev-api-internhasha.wafflestudio.com
  http:
    - route:
        - destination:
            host: internhasha-server

misc/apps/namespace.yaml

  • 리소스 배포에 필요한 namespace를 정의한다.
  • 앞서 살펴봤듯 istio-injection을 통해 사이드카 프록시 주입을 설정하여 istio 리소스의 라우팅 기능이 동작하도록 한다.
# ...
---
apiVersion: v1
kind: Namespace
metadata:
  name: internhasha-dev
  labels:
    istio-injection: enabled
---
apiVersion: v1
kind: Namespace
metadata:
  name: internhasha-prod
  labels:
    istio-injection: enabled

PR 타임라인

#236. Add internhasha-server

  • 최초 배포 PR
  • Spring profile 환경변수가 누락되었음(default 프로필로 실행)
  • ServiceAccount가 정의되어 있지 않아 IRSA를 통한 Secrets Manager 접근이 불가능했음
  • 무엇보다 메모리 할당량을 dev, prod 각각 128mi, 256mi로 설정하여 Spring 앱 구동이 OOM으로 실패, Pod을 계속 새로 다시 만드는 문제상황이 발생하였음

#237. 인턴하샤 서버에 profile & service account 추가

  • ServiceAccount와 Spring profile 환경변수를 추가
  • 정작 Deployment에 해당 serviceAccountName를 사용하지 않아 여전히 IRSA 기능 불가능
  • OOM으로 인한 크래쉬를 인지하지 못 한 상태, Pod 재생성 무한시도는 계속되었음
  • 직후 커밋에서 replicas:0으로 설정되며 일단 pod 생성을 멈추었음

#238. 인턴하샤 deployment에 service account 추가

  • 'Deployment'에 serviceAccountName 추가
  • 이로써 IRSA 설정은 완료되었지만 여전히 OOM으로 컨테이너 시작 자체가 되지 않아 검증이 불가능했음

#240. 인턴하샤 서버에 JAVA_OPTS 추가

  • OOM을 최초로 인지하였고, 이를 방지하기 위해
    • JAVA_OPTS로 힙 사용량을 컨테이너 할당량의 80%로 제한
    • dev 서버의 메모리 할당량을 128mi에서 256mi로 증가
  • JAVA_OPTS 문자열을 제대로 적지 못 해 ArgoCD에서 에러를 발생시킴
    - name: JAVA_OPTS
      value: "-XX:InitialRAMPercentage=80.0 -XX:MaxRAMPercentage=80.0  # "가 닫히지 않음

#241. 인턴하샤 - JAVA_OPTS 필드 문법 교정

  • JAVA_OPTS 문자열 수정버전
  • 여전히 OOM이 발생

#242. 인턴하샤 - CPU, 메모리 리소스 증가

  • prod, dev 서버 모두 메모리 할당량을 256mi에서 768Mi로 대폭 증가
  • 최초 배포에서 발생했던 IRSA, OOM 문제가 해결되었음!

#243. dev 서버 메모리 조정

  • 이후 dev 서버를 지속적으로 업데이트하는 과정에서 어느날 갑자기 pod 생성에 실패하였음
  • 당시 ArgoCD의 로그

    0/7 nodes are available: 2 Insufficient memory, 2 node(s) didn't match Pod's node affinity/selector, 3 node(s) had untolerated taint {phase: prod}. preemption: 0/7 nodes are available: 2 No preemption victims found for incoming pod, 5 Preemption is not helpful for scheduling.

  • 로그 해석
    • 0/7 nodes are available: 현재 7개의 노드 중 이 파드를 배포할 수 있는 노드가 0개
    • 2 Insufficient memory: 2개 노드는 메모리 부족
    • 2 node(s) ~ untolerated taint {phase: prod}: phase=dev 라벨이 붙은 노드에만 올라갈 수 있는 dev 파드는 5개의 phase=prod 라벨이 붙은 노드에 올라가지 못 함(2+3)
    • 나머지 로그: 선점(다른 파드 종료)을 시도했으나 실패
  • 해결방법: dev Deployment의 메모리 할당량을 768mi에서 512mi로 축소
    → 메모리 문제를 해결하며 phase=dev 라벨이 붙은 노드에 성공적으로 추가되었음!

cf. DataSource 연결

RDS

  • 기본적으로 동아리 내부에서는 단일 RDS 인스턴스를 사용하고 있고, 퍼블릭 엑세스를 허용하고 있기 때문에 데이터베이스의 url 및 각 테이블 접근에 필요한 username, password를 Secrets Manager에 추가하여 값을 가져와 연결하는 방식으로 구현하였다.

Redis

  • AWS ElastiCache (Redis)는 기본적으로 VPC 내에서만 접근 가능하기 때문에 별도의 설정 없이 Redis url, database 번호만 환경변수(속성)에 추가하여 접속이 가능하도록 구현하였다.
  • dev와 prod 모두 동일한 database를 사용하기 때문에 key에 prefix(internhasha:dev, internhasha:prod)를 추가하여 저장된 데이터를 구분하도록 하였다.

⇒ 두 리소스를 접근하는데에는 별도의 IRSA 설정이 요구되진 않았다.


서버 배포를 마무리하며

  • CLI로 모든 리소스를 직접 조회, 관리하며 배포할 수 있었던 ec2와 달리 ArgoCD는 선언적 방식으로 모든 리소스가 미리 정의하는 방식이고, kubectl를 이용한 CLI 접근도 불가능하기 때문에 훨씬 제한적이고 엄격한 상황에서 배포가 이루어지는 느낌이었다.
  • PR로만 git repo를 업데이트할 수 있어 실수, 오타로 인한 대응이 즉각적으로 이루어지기 어려웠고, 그런 상황을 마주하니 PR 올릴 때마다 스스로의, 또는 인프라팀의 리뷰가 더욱 중요하고 세심하게 이루어져야 할 것 같았다.
  • 한 번 안정적으로 리소스를 올린 후에는 리소스 관리, 자동 배포 등을 포함한 모든 orchestration을 EKS에서 알아서 관리해주기 때문에 그 편리함을 조금이나마 느낄 수 있었다.

0개의 댓글