728x90
반응형

[목표]

현재 팀원들이 개발을 진행중인 여러 개의 Application들을 Main branch에 push를 하게 되면 자동으로 쿠버네티스 서버에 배포가 되게 하도록 해야합니다.

[Tool]

  • Gitea : Git은 Gitlab보다 가벼운 Gitea(깃티)라는 소프트웨어 패키지를 사용하고 있습니다.
  • Jenkins : 빌드된 파일들을 Dockerfile을 이용해서 이미지로 만드는 등의 Pipeline CI/CD 작업 툴로 Jenkins를 사용합니다.
  • ArgoCD : ArgoCD는 쿠버네티스 배포를 도와주는 도구입니다.
  • Kubernetes : Microservice를 구현할 수 있게 컨테이너화 된 애플리케이션을 관리합니다.
  • Helm : Kubernetes 패키지 관리를 위하여 사용합니다.

[전체 과정]

Gitea와 Jenkins 연동

1. Jenkins에 Gitea관련 설정 추가

Jenkins에 Gitea를 사용할 수 있는 Plugin을 설치해야 합니다.
https://plugins.jenkins.io/gitea-checks/
https://plugins.jenkins.io/gitea/

2. Gitea 연결 (Jenkins 관리 > 시스템 설정)

  • Name : 사용할 이름을 정해주면 됩니다. gitea로 설정하였습니다.
  • Server URL : Gitea의 URL을 입력해야 합니다. (Gitea 설정이 정상적으로 되었다면 아래에 조그만 글씨로 Gitea Version 정보가 나타나게 됩니다.)
    • Credentials 설정 (Jenkins관리 > Credentials > System > Global credentials)에서 Add Credentials를 클릭합니다.
    • Username with password를 통해 설정합니다. (root / cloud1234)
      • (이미 설정하였기에 This ID is already in use 가 생기고, Unique한 값으로 설정해줍니다)
      • Credentials는 구축된 Gitea에 접근하기 위한 인증입니다. Jenkins는 기본적으로 Credentials Plugin을 제공하며, 이를 통해 인증 정보를 관리하고 사용합니다.Manage hooks : Credentials를 지정해주어야 합니다.

3. Jenkins Webhook 설정

  • Multibranch Pipeline Project로 생성해야 합니다.

(Gitea 연결은 다른 Freestyle Project 또는 Pipeline으로는 연결이 불가능합니다.)

 

    • Multibranch Pipeline을 통해 아래와 같이 설정해줍니다.
      • Gitea Server는 gitea 서버를 설정해줍니다.
      • gitea 서버를 설정하면 아래 Repository 목록이 나올 것입니다. 원하는 Repository를 선택하면 됩니다.

  • Build Configuration
    • Mode는 Jenkinsfile로 설정합니다. 이후에 Webhook은 설정한 Repository에 있는 Jenkinsfile을 실행하게 됩니다
  • Scan Multibranch Pipeline Triggers
    • Scan by webhook에서 Trigger token은 이후에 Gitea에서 Webhook 설정을 할 때 header에 담을 token과 동일하게 설정해야 하기 때문에 기억한 뒤 Gitea Webhook 설정에 사용해야합니다.
    • 4insure-msa-app으로 설정해두었습니다.

4. Gitea Webhook 설정

    • Gitea에서 Repository의 설정 > Webhooks 에서 Webhook을 추가하면 됩니다.
      (Setting이 보이지 않는다면 해당 Repository에 권한이 없기 때문입니다)
    • Target URL
      • http://{Jenkins URL}/multibranch-webhook-trigger/invoke?token={토큰 값}
      • 토큰 값은 위에 Jenkins Webhook 설정 때 만들었던 token입니다.

5. Jenkinsfile 작성

  • Jenkinsfile을 해당 Repository의 main branch에 commit & push해주면 Webhook이 동작하고 Jenkins Pipeline이 동작합니다.

 

Jenkins Pipeline 작성

1. Maven 등록 (Jenkins 관리 > Tools)

  • Jenkins가 실행되면 Jenkins에게 Build Tool를 주어야 Build 할 수 있습니다.
  • Maven를 빌드 툴로 설정하였기 때문에 아래와 같이 설치합니다.

2. Jenkins WorkFlow

import java.text.SimpleDateFormat
def dateFormat = new SimpleDateFormat("yyMMdd_HHmmss")
def TAG = dateFormat.format(new Date())
pipeline {
    agent any
    tools {
        maven "M3"
    }
    environment {
        REGISTRY_HOST = 'msa.harbor.com'
        REGISTRY_PROJECT = 'conn-test'
        GIT_OPS_REPOSITORY = 'msa-gitops'
        TAG = 'latest'
        APP = '4insure-msa-api-user'
        APP_PATH = 'UserApi'
        NAMESPACE = 'portal'
        PROFILE = 'msa' //'prod'
        ENV = 'msa'
    }
    stages {
        stage('echo test') {
            steps {
                echo "4insure-msa-api-user app"
            }
        }
        stage('Maven Build') {
            steps {
                 sh 'ls -al' // Run Maven build
                 sh 'mvn -f pom.xml clean install' // Run Maven build
            }
        }
        stage('Docker Build & Push') {
            steps {
                sh "docker login ${REGISTRY_HOST} -u admin -p cloud1234"
                sh "docker build -t ${REGISTRY_HOST}/${REGISTRY_PROJECT}/${APP}:${TAG} ."
                sh "docker push ${REGISTRY_HOST}/${REGISTRY_PROJECT}/${APP}:${TAG}"
            }
        }
        stage('Clean dummy images') {
            steps {
                sh """docker image prune --all --force --filter label=app=${APP} --filter label=io.kubernetes.pod.namespace=jenkins"""
            }
        }
        stage('Remote api updating gitops job') {
            steps {
                sh """curl -X GET -u \"admin:cloud1234\" 'http://172.10.40.243:30070/job/msa-gitops/buildWithParameters?token=MSA_GITOPS_TOKEN&TARGET=${APP}&TAG=${TAG}&ENV=${ENV}'"""
            }
        }
    }   // End of stages
} // End of pipeline

Source Build

  • Source Repository에서 Maven을 이용해 해당 애플리케이션을 Build 합니다

Docker Build & Push

  • Build된 것을 해당 Repository의 Dockerfile을 이용해서 Image를 만듭니다.
  • 만들어진 이미지를 Harbor에 Push합니다.
FROM neduekwunife/openjdk8-jre-alpine-with-fontconfig
LABEL app="4insure-msa-api-user"
ADD ./target/UserApi-3.0.jar /
WORKDIR /
ENV SPRING_PROFILES_ACTIVE=msa
ENTRYPOINT ["java", "-Dspring.profiles.active=${SPRING_PROFILES_ACTIVE}", "-Dfile.encoding=UTF8", "-Duser.timezone=Asia/Seoul", "-jar", "UserApi-3.0.jar"]

 

Clean Docker Image

  • 필요없는 docker image를 삭제합니다.
  • namespace를 제대로 설정해야 정확한 image를 타겟팅하여 삭제할 수 있습니다.

Update GitOps

  • 새롭게 만들어진 Image를 배포하기 위해 Jenkins의 GitOps Job를 호출해서 실행해야 합니다.
  • Jenkins의 원격 빌드를 이용하여 빌드를 유발합니다.

3. Update GitOps

    • 새로운 Item 생성을 통해 Job을 생성합니다. 이번에는 Pipeline을 선택합니다.
    • 매개변수가 있기 때문에 매개변수를 등록하여 사용할 수 있도록 합니다.
      • Jenkins의 매개변수 설정 중 Trim the string 을 해주어야 합니다.
    • 소스코드 관리
      • Gitea에 GitOps를 위한 새로운 Repository를 생성합니다. 이름은 msa-gitops로 하였습니다
      • 소스코드 관리 탭에서 Repository URL과 Credentials를 설정해줍니다

  • 빌드 유발
    • 빌드를 원격으로 유발을 클릭하고 Authentication Token을 등록해줍니다.
    • 해당 Token값은 위에서 마지막 Stage인 Update GitOps를 작성할 때 token={TOKEN_NAME}의 TOKEN_NAME과 동일하여야 합니다.

Shell Script 내용

  • 위에서 설정한 파라미터들이 shell에서 사용됩니다.
  • Shell은 Helm을 사용하여 Kubernetes에 배포하는 과정을 자동화하는 것을 목적으로 합니다.
  • Image tag는 현재 시간값으로 재설정됩니다. 해당 내용은 위에서 작성한 Jenkins Workflow에서 확인할 수 있습니다.
    • def TAG = dateFormat.format(new Date())
  • Image tag를 바꾼 뒤, GitOps를 하기위한 Repository에 commit, push를 진행합니다.
#!/bin/bash
MODULES=$TARGET
APP_LIST="4insure-msa-api-portal 4insure-msa-api-user 4insure-msa-app"
echo "Deploy target application: "$TARGET
echo "TAG: "$TAG
chmod 777 manifests/$TARGET.yaml
helm template -n test $TARGET charts/$TARGET -f charts/$TARGET/values.yaml \
     --set image.tag=${TAG} > manifests/$TARGET.yaml
echo "TAG changed: "$TAG
# git add/commit/push
git add .
git commit -m "updating manifest file completed."
git status
git push origin HEAD:main

4. ArgoCD

ArgoCD는 GitOps Repository를 바라보고 있습니다.

해당 GitOps Job에 의해 변화된 이미지가 Repository에 push 되었으므로 ArgoCD가 이를 감지하여 Sync를 맞추어 쿠버네티스에 새로운 이미지를 사용하여 자동으로 배포합니다.

 

Helm 설정

Helm Chart

Helm은 쿠버네티스 애플리케이션을 관리하기 위한 패키지 매니저입니다.
npm 패키지 매니저의 package.json이 있듯이 Helm에는 Chart를 통해서 쿠버네티스의 리소스를 설치하는데 필요한 모든 정보를 포함하고 있습니다.
helm chart에는 templates폴더, values.yaml파일이 포함되어 있는데, 차트의 설정 값이 있는 values.yaml파일을 통하여 templates파일들을 렌더링하여 쿠버네티스의 리소스 파일을 생성합니다.
values.yaml파일의 설정값을 사용자가 재정의하여 커스터마이징을 할 수 있습니다.

Gitea Helm Chart Install

# gitea 공식 Helm Repo Add 
helm repo add gitea-charts <https://dl.gitea.com/charts/> 
# Offline Mode 
helm fetch gitea-charts/gitea 
# install 
helm install -n gitea gitea . -f values.yaml
helm repo add {사용자 임의 정의 Repo 이름} {추가할 Helm repository URL}
helm fetch {다운로드 받을 Chart} # 다운로드 받은 파일은 .tgz확장자를 가진 압축파일

Gitea의 공식 Repository를 Helm에 추가하여 Helm을 통해 Gitea 차트를 설치하거나 업데이트할 수 있도록 합니다.

Directory 구성

  • charts
    • templates
      • 쿠버네티스 리소스 yaml 파일들이 위치합니다.
        (Deployment.yaml, Service.yaml을 Values.yaml의 변수를 통해서 작성합니다.)
  • manifests
    • 변경된 yaml 파일이 저장됩니다.
    • ArgoCD는 manifests에 변환된 yaml파일을 감지하여 Sync를 맞추어 자동으로 배포합니다.
  • scripts
    • GitOps Job에서 ArgoCD를 동작시키기 위하여 Helm의 Image를 변경하기 위해 실행되는 script입니다
  • Chart.yaml
    • chart에 대한 메타데이터가 저장되어있습니다.
728x90
반응형
728x90
반응형

Pod : 쿠버네티스의 가장 작은 배포 단위

쿠버네티스는 컨테이너를 직접 관리하지 않고 pod이라는 걸로 감싸서 관리한다.

Pod이 쿠버네티스에서 가장 작은 배포 단위이고, 컨테이너를 배포하는 것이 아니라 'Pod를 배포한다'고 한다.

Pod의 특징

  • 전체 클러스터에서 고유한 IP를 부여받는다.

여러 개의 컨테이너가 하나의 Pod에 속할 수 있다.

  • 첫번째의 경우 Container와 log를 수집하는 컨테이너, 이렇게 2개가 떠있을 수 있고, 호스트에 있는 폴더를 공유할 수 있다.
  • 두번째의 경우 캐시를 같이 띄워서 포트를 공유할 수 있다.

ReplicaSet

  • 여러 개의 Pod을 관리
  • 새로운 Pod는 template을 참고하여 생성
  • 신규 Pod를 생성하거나 기존 Pod를 제거하여 원하는 수의 Replicas를 유지

Deployment

  • 배포 버전을 관리
  • ReplicaSet을 무중단 배포하기 위해서 사용할 수 있다.
  • ReplicaSet을 둘러쌈

쿠버네티스는 다양한 방식을 제공한다.

  • Demonset : 모든 노드에 꼭 하나씩만 떠 있기를 원하는 Pod을 만들고 싶을 때 사용
    • 로그를 수집하거나 모니터링을 하는 것에 적합
  • StatefulSet
    • 순서대로 Pod을 실행하고 싶거나 같은 볼륨을 계속 재활용하고 싶을 때
  • Job
    • 한번 실행하고 죽는 종류의 Pod을 만들 수 있다.

쿠버네티스 네트워크

쿠버네티스는 네트워크도 별도의 오브젝트로 관리한다.

Cluster IP

서비스 중에서도 ClusterIP 라는 것은 Pod들(여기서는 3개)를 로드밸런서 하는 별도의 서비스이다.

ClusterIP로 요청을 보내면 자동으로 저 3개 Pod 중에 하나로 요청이 가게 되는 것이다.

이렇게 하는 이유는 Deployment나 ReplicaSet을 보면 Pod이 업데이트 될 때, 그 IP를 유지한 상태로 업데이트 되는 것이 아니라 그냥 Pod이 죽고 새로운 Pod이 뜨는 개념이기 때문에 IP가 언제든지 사라졌다가 바뀌거나 하는 것이 자연스럽다.

그래서 요청을 보낼 때, Pod으로 바로 보내는 것이 아니라 Service라는 것을 먼저 만들고 Service의 IP는 고정이니까 거기에 요청을 보내면 뒤에 Pod은 늘던 줄던 Ip가 바뀌든 상관없이 정상적으로 원하는 Pod에 요청을 보낼 수 있기 때문에 ClusterIP 서비스를 붙이게 된다.

내부적으로는 이렇게 통신을 하게 된다.

만약 웹 서버에서 Redis로 통신을 하고 싶다면 내부 DNS에서 Redis라는 도메인이 생긴다.

그러면 Redis로 요청을 보내면 ClusterIP로 요청을 보내고, 그 요청이 다시 안쪽에 있는 Pod으로 요청이 가게 된다.

웹에서 mysql로 요청을 보내면 1차적으로 ClusterIP로 요청이 간 다음에 그 요청이 다시 mysql의 pod으로 요청이 간다.

그런데 ClusterIP는 내부에서만 통신할 수 있다. (외부 브라우저로는 접근할 수 없다.)

그래서 외부에서 접근하기 위해서 NodePort라는 개념이 생긴다.

Node에 Port가 생기고, 거기로 접속을 하면 그 요청이 다시 ClusterIP로 가고 그 요청이 다시 또 Pod로 가는 그런 구조로 되어 있다.

모든 노드에 동일한 포트로 되어있다.

웹브라우저에서 요청 → NodePort → ClusterIP → 내부에 있는 Pod들


만약에 Node가 2개가 있고, NodePort를 생성하게 되면 1번 Node에도 생기고 2번 Node에도 생기게 된다. 그래서 1번 Node로 요청을 보내도 원했던 ClusterIP로 요청이 가고, 2번 Node에도 요청을 보내도 원했던 ClusterIP로 요청이 가게 된다.

예를 들어 Node가 10개면 10개 중에 아무데나 보내도 내부에 있는 네트워크를 잘 찾아가는 그런 기능을 가지고 있다.

문제는 만약 내가 1번 노드를 도메인으로 연결을 해놓았는데 그런데 갑자기 1번 노드가 죽을 수가 있다. (서버 자체가 사라질 수가 있다. )

그럼 2번 노드로 붙어야 되는데 설정을 1번 노드로 해놓았기 때문에 순간적으로 접속이 안되게 된다.

그래서 이것을 방지하기 위해서 앞에 로드 밸런서를 별도로 둔다.

[흐름]

사용자는 LoadBalancer로 요청을 보내고, 그 요청이 다시 NodePort로 가고, NodePort가 다시 ClusterIP로 가고, ClusterIP가 다시 Pod로 가는 구조로 되어 있다.

Ingress

위의 그림처럼 전부 다 NodePort를 만들거나 전부 다 LoadBalancer를 만들면 자원이 낭비된다.

Ingress 하나를 만들고, Ingress 내부에서 Service를 분기할 수 있다.

이전에 설명한 네트워크는 IP Port로만 접속을 하는 것이라면 Ingress는 도메인 이름, 그리고 도메인 뒤에 붙는 path에 따라서 내부에 있는 ClusterIP 연결을 할 수 있다.

예를 들어서, example.com, kluster.com/blog, kluster.com/news 이런 3개의 요청이 들어왔을 때 이 세개를 전부 다 NodePort를 만들거나 전부 다 LoadBalancer를 만들면 그 자원이 낭비가 된다.

그 대신에 Ingress 하나만 만들고 그 Ingress가 내부적으로 서비스를 분기할 수 있다.

Ingress는 뭔가 새로 만든다기보다는 기존에 있었던 Nginx, HAProxy, ALB 이런 것들을 재활용해서 쿠버네티스에 맞게 포장을 해서 사용을 한다.

일반적으로 Pod만 띄우는 경우는 잘 없다. Deployment를 생성을 하고, Deployment가 ReplicaSet을 자동으로 생성하고, ReplicaSet이 자동으로 Pod를 생성하는 이런 구조를 갖는다.

그리고 이것을 외부로 노출시키기 위해서 Service(ClusterIP)를 하나 붙인다.

그리고 서비스를 붙인 다음에 Ingress를 붙인다.

Ingress를 붙이면 NodePort랑 LoadBalancer가 자동으로 따라온다.

그래서 저걸 붙인 다음에 실제 클라이언트는 이제 저 도메인으로 접속을 하게 되면은 저 LoadBalancer를 거쳐서 NodePort를 거쳐서 ClusterIP를 거쳐서 Pod로 연결되는 것이 일반적인 방법이다.

그 외 기본 오브젝트

Volume - Storage(EBS, NFS, …)

Namespace - 논리적인 리소스 구분

  • 내부적으로 리소스를 네임스페이스로 구분할 수 있다.

ConfigMap / Secret - 설정

ServiceAccount - 권한 계정

Role / ClusterRole - 권한 설정 (get, list, watch, create …)

 

API 호출

그럼 쿠버네티스는 실제로 어떻게 API를 호출할까?

Pod이라는 오브젝트를 띄우고 싶다, 생성하고 싶다면 yaml로 사용해서 생성한다.

apiVersion: v1
kind: Pod
metadata:
  name: example
spec:
  containers:
  - name: busybox
    image: busybox:1.25

명세서 작성을 하면 컨테이너가 동작을 하게 된다.

명세서를 API Server가 보고 etcd에 저장을 하고, 각 Controller들이 동작하게 된다.

API 호출한다 == 원하는 상태(desired state)를 다양한 오브젝트(object)로 정의(spec)하고 API 서버에 yaml형식으로 전달하는 것

728x90
반응형

+ Recent posts