IaC

EKS 환경에서 오브젝트(Sonarqube, Argocd) 배포 -1

jih0ssang 2024. 12. 6. 09:56

CloudFormation을 통해 VPC, EC2, EKS 등 인프라를 생성하고

EC2의 Shell Script를 통해 EKS 오브젝트(EKS Pod, Ingress)를 배포할 예정이다.

 

1.  사전 작업

IaC(Cloudformation) 기반으로 인프라를 생성한다.

Private EC2를 생성하여 EKS를 관리하고자 한다.

메인 작업을 위해 EC2에 필요한 솔루션 설치 및 IAM Role 설정하여 환경 구성을 한다.

모든 작업은 IaC 및 Script 기반으로 수행한다. (코드는 github 게시 예정)

1.1 EC2 생성

1.1.1 eksctl 설치

https://wrynn.tistory.com/62

curl --silent --location "https://github.com/weaveworks/eksctl/releases/latest/download/eksctl_$(uname -s)_amd64.tar.gz" | tar xz -C /tmp

mv /tmp/eksctl /usr/local/bin

eksctl version
> 0.200.0

 

1.1.2 kubectl 설치

https://docs.aws.amazon.com/eks/latest/userguide/install-kubectl.html

kubectl version --client

 

# Linux
# Kubernetes 1.31
curl -O https://s3.us-west-2.amazonaws.com/amazon-eks/1.31.2/2024-11-15/bin/linux/amd64/kubectl

mkdir -p $HOME/bin 
cp ./kubectl $HOME/bin/kubectl 
export PATH=$HOME/bin:$PATH

 

1.1.3 helm 설치

# 1. 스크립트 다운로드
curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3

# 2. 다운받은 스크립트에 실행 권한 부여
chmod 700 get_helm.sh

# 3. 스크립트 실행하여 Helm 설치
./get_helm.sh

# 4. 설치 확인
helm version

1.2 IAM Role 생성

Priavate EC2가 EKS Cluster 접근을 위한 IAM Role을 생성한다.

 

 

해당 IAM Role에 할당할 Policy 목록

  • AmazonEKS_CNI_Policy
  • AmazonEC2ContainerRegistryReadOnly
  • AmazonEKSWorkerNodePolicy
  • AmazonEC2RoleforSSM 

AmazonEC2RoleforSSM은 Private EC2를 SSM을 통해 접근하기 위한 용도이다.

나머지 정책들은 Private EC2에서 EKS로의 접근을 위한 용도이다.

 

위의 화면처럼 IAM Role을 생성하면 된다.

 

 

1.3 Private EC2의 인스턴스 프로파일에 해당 IAM Role 할당

앞서 생성한 IAM Role을 Private EC2에 할당하고자 한다.

 

1.3.1 IAM 역할 수정

인스턴스 프로파일을 앞서 생성한 IAM Role로 연결한다.

앞으로 Private EC2는 해당 Role을 통해 EKS 접근하게 된다.

 

1.4 EKS Cluster의 IAM 액세스 항목에 해당 IAM Role 할당

 

EKS Cluster의 IAM 액세스 항목에서 앞서 생성한 EC2의 IAM Role을 할당해줘야 최종적으로 접근이 가능하다.

 

1.4.1 IAM 보안 주체에 해당 IAM Role 할당

1.4.2 액세스 정책 지정 - AmazonEKSAdminPolicy

단순 Pod 관리를 위해서는 해당 정책이 필요하다.

 

1.4.3 액세스 정책 지정 - AmazonEKSClusterAdminPolicy

Worker Node 관리를 위해서 해당 정책도 필요하다.

 

액세스 범위는 클러스터로 지정한다.

각각 정책 추가 버튼을 누르고 다음을 클릭한다.

 

1.5 SSM을 통해 Private EC2 접근

1.5.1 kubeconfig 생성

Private EC2를 SSM을 통해 접근하여 kubeconfig를 생성해야 한다.

EKS 클러스터가 생성되었다면 kubectl 명령어를 사용하여 쿠버네티스 클러스터에 접근하기 위해 config 파일을 생성해야 한다.

aws eks --region ap-northeast-2 update-kubeconfig --name [EKS cluster명]

 

이 이후, kubectl get pods -A 또는 kubectl get nodes 명령어 수행이 가능하다.

 

 

2. 메인 작업

Worker Node에 Label 추가 (Option)

# Node의 label 지정
kubectl label node <NODE_NAME> node-type=private

# Node의 label 확인
kubectl get node <NODE_NAME> --show-labels | grep private

# Node의 label 제거
kubectl label nodes <NODE_NAME> node-type=private

Label을 기반으로 Ingress 관련된 오브젝트만 Public Node에 두고 나머지 오브젝트들은 Private Node에 배포할 예정이다.

위의 명령어는 수동으로 Node에 Label을 지정하는 방식이다.

 

 

아래의 내용은 자동으로 Node에 Label을 할당하는 쉘 스크립트이다.

#!/bin/bash

# public nodegroup 노드에 라벨 추가
for node in $(kubectl get nodes -l 'eks.amazonaws.com/nodegroup=Practice2-public-nodegroup' -o name); do
    echo "Labeling public node: $node"
    kubectl label $node node-type=public --overwrite
done

# private nodegroup 노드에 라벨 추가
for node in $(kubectl get nodes -l 'eks.amazonaws.com/nodegroup=Practice2-private-nodegroup' -o name); do
    echo "Labeling private node: $node"
    kubectl label $node node-type=private --overwrite
done

# 결과 확인
echo -e "\nVerifying node labels:"
kubectl get nodes --show-labels | grep "node-type"

 

추가적으로 서비스별 Deployment에 nodeSelector / tolerations 설정을 해줘야 한다.

nodeSelector 만 있으면 안된다...tolerations와 함께 작성해야 원하는 노드 배치가 된다.

 

 

2.1 ArgoCD 배포

# 작업 디렉토리 이동
cd /k8s/argocd

# 설치 파일 다운로드
curl -O https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

 

2.1.1 설치 파일 수정 - ConfigMap

apiVersion: v1
kind: ConfigMap
metadata:
  labels:
    app.kubernetes.io/name: argocd-cmd-params-cm
    app.kubernetes.io/part-of: argocd
  name: argocd-cmd-params-cm
data:
  server.insecure: "true"

설치 파일 중 ConfigMap의 data.server.insecure: "true" 설정을 추가하면 tempoarary redirect 에러 해결이 가능하다.

 

2.1.2 설치 파일 수정 - Deployment, Statefulset

모든 Deployment와 Statefulset에 아래의 nodeSelector, tolerations를 기입한다.

 

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app.kubernetes.io/component: applicationset-controller
    app.kubernetes.io/name: argocd-applicationset-controller
    app.kubernetes.io/part-of: argocd
  name: argocd-applicationset-controller
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: argocd-applicationset-controller
  template:
    metadata:
      labels:
        app.kubernetes.io/name: argocd-applicationset-controller
    spec:	# -------아래 코드 복사-------
      nodeSelector:
        node-type: "private"
      tolerations:
        - key: "node-type"
          operator: "Equal"
          value: "private"
          effect: "NoSchedule"

 

# 배포
kubectl apply -n argocd -f install.yaml

 

 

---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: argocd-ingress
  namespace: argocd
  annotations:
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}]'
    alb.ingress.kubernetes.io/group.name: "cdscamp"
spec:
  ingressClassName: alb
  rules:
    - host: argocd.biz.biz
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: argocd-server
                port:
                  number: 80

 

 

배포 성공

초기 아이디: admin

초기 비밀번호는 아래의 명령어를 통해 확인 가능하다.

kubectl get secret argocd-initial-admin-secret -n argocd -o jsonpath="{.data.password}" | base64 -d; echo

 

2.2 Sonarqube 배포

Sonarqube는 코드 정적 분석 도구이다.

필요한 오브젝트

  • manifest
    • Deployment (sonarqube-deployment.yaml)
    • Service (sonarqube-service.yaml)
  • Secret (sonarqube-secret.yaml)
  • PVC (sonarqube-pvc.yaml)와  PV (sonarqube-pv.yaml)
  • progreSQL DB
    • Deployment (sonarqube-postgre-deployment.yaml)
    • Service (sonarqube-postgre-service.yaml)

PostgreSQL 배포 순서

1.  Namespace 생성

kubectl create namespace monitoring

2. PV (PersistentVolume) 구성

kubectl apply -f sonarqube-postgre-pv.yaml

3. PVC (PersistentVolumeClaim) 구성

kubectl apply -f sonarqube-postgre-pvc.yaml

 

4. Secret 구성

kubectl apply -f sonarqube-postgre-secret.yaml

5. PostgreSQL Service 구성

kubectl apply -f sonarqube-postgre-service.yaml

6. PostgreSQL Deployment 구성

kubectl apply -f sonarqube-postgre-deployment.yaml

 

Sonarqube 배포 순서

7. Sonarqube Service 구성

kubectl apply -f sonarqube-service.yaml

8. Sonarqube Deployment 구성

kubectl apply -f sonarqube-deployment.yaml

 

# Helm 저장소 추가
helm repo add sonarqube https://SonarSource.github.io/helm-chart-sonarqube

# Helm 저장소 업데이트
helm repo update

# 공식 Helm 리포지토리에서 sonarqube 차트의 values.yaml 다운로드
sudo touch values.yaml && sudo chmod 666 values.yaml
helm show values sonarqube/sonarqube > values.yaml

# 저장소 목록 확인
helm repo list

# Sonarqube chart 검색
helm search repo sonarqube

배포 방식은 value.yaml 파일을 받아서 수정하는 방식, yaml을 직접 작성하는 방식.. 다양하다.

나는 둘다 진행했지만 최종적으로 yaml을 직접 작성하는 방식으로 채택하였다.

2.2.1 value.yaml 파일 수정

controller.nodeSelector.eks.amazonaws.com/nodegroup: Practice2-private-nodegroup

 

2.2.1.1 namespace 지정

namespace: test

 

 

2.2.1.2 Port 지정 (Option)

Sonarqube는 기본 9000 포트지만 9010으로 변경하고자 한다.

9000 포트 -> 9010포트 변경

 

externalPort: 서비스가 외부에 노출하는 포트
internalPort: 컨테이너가 수신하는 포트

 

2.2.1.3 postgresql 확인

Sonarqube는 postgresql을 사용한다.

정상적으로 enabled: true 되어있는지 설정 확인한다.

 

2.2.1.4 PV 지정

 

2.2.1.5 Ingress / Ingress Controller 지정

ingress-nginx.enabled: true

controller.nodeSelector.eks.amazonaws.com/nodegroup: Practice2-public-nodegroup

 

ingress 관련 리소스는 public nodegroup에만 배포되도록 nodeSelector 설정한다.

기존 Ingress만 활용할 것이므로, httproute는 그대로 둔다.

httproute는 기존 Ingress보다 더 발전된 라우팅 규칙을 지원하는 아직 베타 단계의 기능이다.

 

 

ingress.enabled: true

ingress.hosts.name: 원하는 도메인 (ex. sonarqube.userid.biz.biz )

ingress.hosts.path: /

ingress.hosts.pathType: Prefix

 

ingress.annotations.kubernetes.io/ingress.class: nginx

ingress.annotations.nginx.ingress.kubernetes.io/ssl-redirect: "false" 

ingress.ingressClassName: nginx

 

# helm 차트에서 받아온 파일과 내가 작성한 파일 병합(옵션 -f)
helm install sonarqube sonarqube/sonarqube -f values.yaml -n test

# 설치 확인
kubectl get pods -n test
kubectl get svc -n test
kubectl get ingress -n test

 

helm 릴리스 삭제
$ helm uninstall sonarqube -n test

 

배포 성공

 

2.2.2 직접 yaml 파일 작성

apiVersion: v1
kind: Namespace
metadata:
  name: sonarqube
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: sonar-postgresql-pv
spec:
  storageClassName: local-storage
  accessModes:
  - ReadWriteOnce
  capacity:
    storage: 5Gi
  persistentVolumeReclaimPolicy: Retain
  hostPath:
    path: /k8s/sonarqube
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: sonarqube-postgresql-pvc
  namespace: sonarqube
spec:
  storageClassName: local-storage
  volumeName: sonar-postgresql-pv
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
---
apiVersion: v1
kind: Secret
metadata:
  name: postgres-pwd
  namespace: sonarqube
type: kubernetes.io/basic-auth
stringData:
  password: P@s\$w0rd
---
apiVersion: v1
kind: Service
metadata:
  name: sonarqube-postgres-svc
  namespace: sonarqube
spec:
  selector:
    app: sonarqube-postgres
  ports:
    - port: 5432
      targetPort: 5432
      protocol: TCP
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sonarqube-postgres
  namespace: sonarqube
spec:
  replicas: 1
  selector:
    matchLabels:
      app: sonarqube-postgres
  template:
    metadata:
      labels:
        app: sonarqube-postgres
    spec:
      nodeSelector:
        node-type: "private"
      tolerations:
        - key: "node-type"
          operator: "Equal"
          value: "private"
          effect: "NoSchedule"
      securityContext:
        runAsUser: 0
        fsGroup: 0   
      containers:
        - image: postgres:14.5
          name: sonarqube-postgres
          resources:
            requests:
              cpu: "100m"
              memory: "256Mi"
            limits:
              cpu: "300m"
              memory: "512Mi"
          env:
            - name: POSTGRES_USER
              value: sonar
            - name: POSTGRES_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: postgres-pwd
                  key: password
          ports:
            - containerPort: 5432
              name: postgresport
          volumeMounts:
            - name: postgres-data
              mountPath: /var/lib/postgresql/data
              subPath: postgres-data
      volumes:
        - name: postgres-data
          persistentVolumeClaim:
            claimName: sonarqube-postgresql-pvc
---
apiVersion: v1
kind: Service
metadata:
  name: sonarqube-svc
  namespace: sonarqube
spec:
  selector:
    app: sonarqube
  ports:
  - name: http
    port: 9000
    protocol: TCP
    targetPort: 9000
  type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sonarqube
  namespace: sonarqube
spec:
  replicas: 1
  selector:
    matchLabels:
      app: sonarqube
  template:
    metadata:
      labels:
        app: sonarqube
    spec:
      nodeSelector:
        node-type: "private"
      tolerations:
        - key: "node-type"
          operator: "Equal"
          value: "private"
          effect: "NoSchedule"
      initContainers:
      - name: increase-the-vm-max-map-count
        image: busybox:1.28
        command:
        - sysctl
        - -w
        - vm.max_map_count=262144
        securityContext:
          privileged: true
      - name: increase-the-ulimit
        image: busybox:1.28
        command:
        - sh
        - -c
        - ulimit -n 65536
        securityContext:
          privileged: true
      containers:
        - image: sonarqube:8.9.9-community
          name: sonarqube
          resources:
            requests:
              cpu: "300m"
              memory: "1Gi"
            limits:
              cpu: "1000m"
              memory: "2Gi"
          env:
            - name: SONARQUBE_JDBC_USERNAME
              value: sonar
            - name: SONARQUBE_JDBC_URL
              value: jdbc:postgresql://sonarqube-postgres-svc:5432/sonar
            - name: SONARQUBE_JDBC_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: postgres-pwd
                  key: password
          ports:
            - containerPort: 9000
              protocol: TCP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: sonarqube-ingress
  namespace: sonarqube
  annotations:
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}]'
    alb.ingress.kubernetes.io/group.name: "cdscamp"
spec:
  ingressClassName: alb
  rules:
    - host: sonarqube.jerry.domain.domain
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: sonarqube-svc
                port:
                  number: 9000

 

 

배포 성공

최초 로그인 정보: admin / admin

 

2.3 Ingress 배포

2.3.1 정책 생성

# 정책 다운로드
curl -o iam_policy.json https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.6.0/docs/install/iam_policy.json
# 정책 생성
aws iam create-policy --policy-name AWSLoadBalancerControllerIAMPolicy --policy-document file://iam_policy.json

해당 정책은 "elasticloadbalancing" 권한이 부족하다.

그래서 "elasticloadbalancing:*" 정책을 추가하였다.

 

 

2.3.2 IAM ODIC 제공자 연결

# EKS 클러스터에 IAM OIDC(OpenID Connect) 제공자 연결
eksctl utils associate-iam-oidc-provider --region=ap-northeast-2 --cluster=Practice2-eks-cluster --approve
IAM ODIC는 IAM Role과 EKS 클러스터의 ServiceAccount와 연결해주는 제공자 이다.
콘솔상 확인되지 않음

 

2.3.3 Service Account 생성 및 정책 할당

# sa 생성 및 정책 할당
eksctl create iamserviceaccount \
  --cluster <EKS_CLUSTER_NAME>  \
  --namespace kube-system \
  --name aws-load-balancer-controller \
  --attach-policy-arn arn:aws:iam::<ACCOUNT_ID>:policy/AWSLoadBalancerControllerIAMPolicy \
  --approve \
  --region ap-northeast-2

 

2.3.4. Ingress Controller 생성

# helm 저장소 추가 및 업데이트
helm repo add eks https://aws.github.io/eks-charts
helm repo update

# helm chart 설치
helm install aws-load-balancer-controller eks/aws-load-balancer-controller \
  -n kube-system \
  --set clusterName=<EKS_CLUSTER_NAME> \
  --set serviceAccount.create=false \
  --set serviceAccount.name=aws-load-balancer-controller \
  --set region=<REGION> \
  --set vpcId=<VPC_ID>
  
  
# helm chart 제거
helm uninstall aws-load-balancer-controller -n kube-system

helm chart를 활용한 Ingress Controller를 생성한다.

 

 

 

2.3.5 Ingress 생성

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: sonarqube-ingress
  namespace: sonarqube
  annotations:
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}]'
    alb.ingress.kubernetes.io/group.name: "cdscamp"
spec:
  ingressClassName: alb  
  rules:
    - host: sonarqube.userid.biz.biz
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: sonarqube-svc
                port:
                  number: 9000

서비스별로 Ingress를 생성하고 namespace도 동일하게 구성하였다.

하나의 alb를 여러 서비스가 공유하며 배포를 위해 annotations에 group을 지정하였다.

 

리소스가 삭제되지 않는 문제

# ingress의 finalizer 삭제
kubectl patch ingress argocd-ingress -n argocd -p '{"metadata":{"finalizers":[]}}' --type=merge
finalizer가 삭제되지 않는 문제
리소스가 삭제되지 않는 주된 이유는 해당 리소스에 설정된 finalizer가 삭제 작업을 막고 있기 때문입니다. 예를 들어, 특정 리소스가 외부 시스템과 연동되어 있고, 이 연결을 종료하는 과정이 완료되지 않으면 리소스는 삭제되지 않습니다.

 

2.3.6 Route53 도메인 연결

 

배포 성공

 

 

 

2.4 GitLab Runner 배포

다음 글에 이어서 작성 예정..

'IaC' 카테고리의 다른 글

EKS 환경에서 오브젝트(GitLab-Runner) 배포 -2  (0) 2025.01.03
[Terraform] 배포 및 플래그  (1) 2024.06.11
[Terraform] 디렉터리 구조  (0) 2024.06.11
[Terraform] 용어  (0) 2024.04.02
[Terraform] 환경 구성  (1) 2024.04.02