[AWES 3기] 8주차 스터디 내용 정리

ajufresh·4일 전
0

실습 환경 구성

[컨테이터 2대(Jenkins, gogs) : 호스트 OS 포트 노출(expose)로 접속 및 사용 : macOS 사용자]

# 작업 디렉토리 생성 후 이동
mkdir cicd-labs
cd cicd-labs

# cicd-labs 작업 디렉토리 IDE(VSCODE 등)로 열어두기

# 
cat <<EOT > docker-compose.yaml
services:

  jenkins:
    container_name: jenkins
    image: jenkins/jenkins
    restart: unless-stopped
    networks:
      - cicd-network
    ports:
      - "8080:8080"
      - "50000:50000"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - jenkins_home:/var/jenkins_home

  gogs:
    container_name: gogs
    image: gogs/gogs
    restart: unless-stopped
    networks:
      - cicd-network
    ports:
      - "10022:22"
      - "3000:3000"
    volumes:
      - gogs-data:/data

volumes:
  jenkins_home:
  gogs-data:

networks:
  cicd-network:
    driver: bridge
EOT


# 배포
docker compose up -d
docker compose ps

# 기본 정보 확인
for i in gogs jenkins ; do echo ">> container : $i <<"; docker compose exec $i sh -c "whoami && pwd"; echo; done

# 도커를 이용하여 각 컨테이너로 접속
docker compose exec jenkins bash
exit

docker compose exec gogs bash
exit

# Jenkins 초기 암호 확인
docker compose exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword
2d06bfbea9a940179cde274c40e6c07a

# Jenkins 웹 접속 주소 확인 : 계정 / 암호 입력 >> admin / qwe123
open "http://127.0.0.1:8080"

# (참고) 로그 확인 : 플러그인 설치 과정 확인
docker compose logs jenkins -f

Jenkins 컨테이너 초기 설정을 해준다.

[Jenkins]
The open source automation server, support building deploying and automating any project
Jenkins는 다양한 프로젝트의 빌드, 배포, 자동화를 지원하는 강력한 오픈 소스 도구이다.

[주요 기능]

  • 소스 코드 관리: 중앙 리포지토리에서 최신 코드 자동 가져오기
  • 빌드 자동화: 소스 코드 컴파일 및 단위 테스트 실행
  • 패키징: 다양한 유형으로 산출물 패키징
  • 배포: 여러 환경으로 산출물 배포

[핵심 특징]

  • Jenkins 파일: 도메인 특화 언어(DSL)를 사용하여 End-to-End 빌드 수명 주기 구축
  • 파이프라인: 스크립트를 통해 빌드 단계별 태스크 및 하위 태스크 순서 정의

이러한 것들 통해 Jenkins는 소프트웨어 개발의 전체 프로세스를 자동화하여 팀의 효율성과 생산성을 크게 향상시킨다.

[Jenkins 컨테이너에서 호스트에 도커 데몬 사용 설정 (Docker-out-of-Docker)]

# Jenkins 컨테이너 내부에 도커 실행 파일 설치
docker compose exec --privileged -u root jenkins bash
-----------------------------------------------------
id

curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  tee /etc/apt/sources.list.d/docker.list > /dev/null
apt-get update && apt install docker-ce-cli curl tree jq yq -y

docker info
docker ps
which docker

# Jenkins 컨테이너 내부에서 root가 아닌 jenkins 유저도 docker를 실행할 수 있도록 권한을 부여
groupadd -g 2000 -f docker  # macOS(Container)

chgrp docker /var/run/docker.sock
ls -l /var/run/docker.sock
usermod -aG docker jenkins
cat /etc/group | grep docker

exit
--------------------------------------------

# jenkins item 실행 시 docker 명령 실행 권한 에러 발생 : Jenkins 컨테이너 재기동으로 위 설정 내용을 Jenkins app 에도 적용 필요
docker compose restart jenkins

# jenkins user로 docker 명령 실행 확인
docker compose exec jenkins id
docker compose exec jenkins docker info
docker compose exec jenkins docker ps

[Gogs 컨테이너 초기 설정 : Repo(Private) - dev-app , ops-deploy]

# 초기 설정 웹 접속
open "http://127.0.0.1:3000/install" # macOS
  • 데이터베이스 유형 : SQLite3
  • 애플리케이션 URL : http://<각자 자신의 PC IP>:3000/
  • 기본 브랜치 : main
  • 관리자 계정 설정 클릭 : 이름(devops), 비밀번호(계정암호 qwe123), 이메일 입력

조금 기다리면 생성된 것을 확인할 수 있다.

- New Repository 1 : **개발팀용**
    - Repository Name : **dev-app**
    - Visibility : (**Check**) This repository is **Private**
    - .gitignore : **Python**
    - Readme : Default → (Check) initialize this repository with selected files and template
    
    ⇒ Create Repository 클릭 : Repo 주소 확인
    
- New Repository 2 : **데브옵스팀용**
    - Repository Name : **ops-deploy**
    - Visibility : (**Check**) This repository is **Private**
    - .gitignore : **Python**
    - Readme : Default → (Check) initialize this repository with selected files and template
    
    ⇒ Create Repository 클릭 : Repo 주소 확인
    

[저장소 설정]

# (옵션) GIT 인증 정보 초기화
git credential-cache exit

#
git config --list --show-origin

#
TOKEN=ef6c827340b6f26f92b9c48544e16db190f9ef9d

MyIP=< IP>

git clone http://devops:$TOKEN@$MyIP:3000/devops/dev-app.git
Cloning into 'dev-app'...
...

#
cd dev-app

#
git --no-pager config --local --list
git config --local user.name "devops"
git config --local user.email "a@a.com"
git config --local init.defaultBranch main
git config --local credential.helper store
git --no-pager config --local --list
cat .git/config

clone 받은 뒤 config를 출력한 내용이다.


#
git --no-pager branch
git remote -v

# server.py 파일 작성
cat > server.py <<EOF
from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler
from datetime import datetime
import socket

class RequestHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        match self.path:
            case '/':
                now = datetime.now()
                hostname = socket.gethostname()
                response_string = now.strftime("The time is %-I:%M:%S %p, VERSION 0.0.1\n")
                response_string += f"Server hostname: {hostname}\n"                
                self.respond_with(200, response_string)
            case '/healthz':
                self.respond_with(200, "Healthy")
            case _:
                self.respond_with(404, "Not Found")

    def respond_with(self, status_code: int, content: str) -> None:
        self.send_response(status_code)
        self.send_header('Content-type', 'text/plain')
        self.end_headers()
        self.wfile.write(bytes(content, "utf-8")) 

def startServer():
    try:
        server = ThreadingHTTPServer(('', 80), RequestHandler)
        print("Listening on " + ":".join(map(str, server.server_address)))
        server.serve_forever()
    except KeyboardInterrupt:
        server.shutdown()

if __name__== "__main__":
    startServer()
EOF


# (참고) python 실행 확인
python3 server.py
curl localhost
curl localhost/healthz
CTRL+C 실행 종료


# Dockerfile 생성
cat > Dockerfile <<EOF
FROM python:3.12
ENV PYTHONUNBUFFERED 1
COPY . /app
WORKDIR /app 
CMD python3 server.py
EOF


# VERSION 파일 생성
echo "0.0.1" > VERSION

#
tree
git status
git add .
git commit -m "Add dev-app"
git push -u origin main
...

새로운 커밋이 생긴 것을 확인할 수 있다.

[도커 허브]

도커 허브(Docker Hub)는 도커 이미지 원격 저장소이다. 사용자들은 도커 허브에 이미지를 업로드하고, 다른 곳에서 자유롭게 재사용(다운로드)할 수 있다.

[자신의 도커 허브 계정에 Token 발급]

personal token을 발급한 뒤 복사해둔다.

Jenkins CI + K8S(Kind)

docker desktop은 이미 설치되어 있어서 별도로 설치하지 않았다.

[kind 설치]

# Install Kind
brew install kind
kind --version

# Install kubectl
brew install kubernetes-cli
kubectl version --client=true

## kubectl -> k 단축키 설정
echo "alias kubectl=kubecolor" >> ~/.zshrc

# Install Helm
brew install helm
helm version

[작업 소개 (프로젝트, Job, Item)]
1. 작업을 수행하는 시점 Trigger
- 작업 수행 태스크 task가 언제 시작될지를 지시
2. 작업을 구성하는 단계별 태스크 Built step
- 특정 목표를 수행하기 위한 태스크를 단계별 step로 구성할 수 있다.
- 이것을 젠킨스에서는 빌드 스텝 build step이라고 부른다.
3. 태스크가 완료 후 수행할 명령 Post-build action
- 예를 들어 작업의 결과(성공 or 실패)를 사용자에게 알려주는 후속 동작이나, 자바 코드를 컴파일한 후 생성된 클래스 파일을 특정 위치로 복사 등

  • (참고) 젠킨스의 빌드 : 젠킨스 작업의 특정 실행 버전
    • 사용자는 젠킨스 작업을 여러번 실행할 수 있는데, 실행될 때마다 고유 빌드 번호가 부여된다.
    • 작업 실행 중에 생성된 아티팩트, 콘솔 로드 등 특정 실행 버전과 관련된 모든 세부 정보가 해당 빌드 번호로 저장된다.

[k8s Deploying an application with Jenkins(pipeline-ci)]

필요한 파이프라인을 다운로드 받는다.

  • 자격증명 설정 : Jenkins 관리 → Credentials → Globals → Add Credentials
    1. Gogs Repo 자격증명 설정 : gogs-crd
    2. 도커 허브 자격증명 설정 : dockerhub-crd
    3. k8s(kind) 자격증명 설정 : k8s-crd

pipeline {
    agent any
    environment {
        DOCKER_IMAGE = '<자신의 도커 허브 계정>/dev-app' // Docker 이미지 이름
    }
    stages {
        stage('Checkout') {
            steps {
                 git branch: 'main',
                 url: 'http://<자신의  IP>:3000/devops/dev-app.git',  // Git에서 코드 체크아웃
                 credentialsId: 'gogs-crd'  // Credentials ID
            }
        }
        stage('Read VERSION') {
            steps {
                script {
                    // VERSION 파일 읽기
                    def version = readFile('VERSION').trim()
                    echo "Version found: ${version}"
                    // 환경 변수 설정
                    env.DOCKER_TAG = version
                }
            }
        }
        stage('Docker Build and Push') {
            steps {
                script {
                    docker.withRegistry('https://index.docker.io/v1/', 'dockerhub-crd') {
                        // DOCKER_TAG 사용
                        def appImage = docker.build("${DOCKER_IMAGE}:${DOCKER_TAG}")
                        appImage.push()
                        appImage.push("latest")
                    }
                }
            }
        }
    }
    post {
        success {
            echo "Docker image ${DOCKER_IMAGE}:${DOCKER_TAG} has been built and pushed successfully!"
        }
        failure {
            echo "Pipeline failed. Please check the logs."
        }
    }
}

파이프라인 추가한 이후에는 secret으로 image에 대한 접근할 수 있는 정보를 추가하고 deployment를 배포한다.

Jenkins로 배포가 잘 되는 것을 확인할 수 있다. 이번에는 샘플 앱 server.py 코드 변경 → 젠킨스(지금 빌드 실행) : 새 0.0.2 버전 태그로 컨테이너 이미지 빌드 → 컨테이너 저장소 Push ⇒ k8s deployment 업데이트 배포

[Gogs Webhooks 설정 : Jenkins Job Trigger]

[security]
INSTALL_LOCK = true
SECRET_KEY   = j2xaUPQcbAEwpIu
LOCAL_NETWORK_ALLOWLIST = # IP
  • Payload URL : http://<자신의 집 IP>:8080/gogs-webhook/?job=SCM-Pipeline/
  • Content Type : application/json
  • Secret : qwe123
  • When should this webhook be triggered? : Just the push event
  • Active : Check

Add webhook

pipeline {
    agent any
    environment {
        **DOCKER_IMAGE** = '***<자신의 도커 허브 계정>***/**dev-app**' // Docker 이미지 이름
    }
    stages {
        stage('Checkout') {
            steps {
                 git branch: 'main',
                 url: '**http://*<자신의  IP>*:3000/devops/dev-app.git**',  // Git에서 코드 체크아웃
                 credentialsId: '**gogs-crd**'  // Credentials ID
            }
        }
        stage('Read VERSION') {
            steps {
                script {
                    // VERSION 파일 읽기
                    def version = readFile('**VERSION**').trim()
                    echo "Version found: ${version}"
                    // 환경 변수 설정
                    **env.DOCKER_TAG = version**
                }
            }
        }
        stage('Docker **Build** and **Push**') {
            steps {
                script {
                    docker.withRegistry('https://index.docker.io/v1/', '**dockerhub-crd**') {
                        // DOCKER_TAG 사용
                        def appImage = docker.**build**("${DOCKER_IMAGE}:${DOCKER_TAG}")
                        appImage.**push**()
                        **appImage.push("latest")**
                    }
                }
            }
        }
    }
    post {
        success {
            echo "Docker image ${DOCKER_IMAGE}:${DOCKER_TAG} has been built and pushed successfully!"
        }
        failure {
            echo "Pipeline failed. Please check the logs."
        }
    }
}
pipeline {
    agent any
    environment {
        **DOCKER_IMAGE** = '***gasida***/**dev-app**' // Docker 이미지 이름
    }
    stages {
        stage('Checkout') {
            steps {
                 git branch: 'main',
                 url: '**http://*192.168.254.127*:3000/devops/dev-app.git**',  // Git에서 코드 체크아웃
                 credentialsId: '**gogs-crd**'  // Credentials ID
            }
        }
        stage('Read VERSION') {
            steps {
                script {
                    // VERSION 파일 읽기
                    def version = readFile('**VERSION**').trim()
                    echo "Version found: ${version}"
                    // 환경 변수 설정
                    **env.DOCKER_TAG = version**
                }
            }
        }
        stage('Docker **Build** and **Push**') {
            steps {
                script {
                    docker.**withRegistry**('https://index.docker.io/v1/', '**dockerhub-crd**') {
                        // DOCKER_TAG 사용
                        def appImage = docker.**build**("${DOCKER_IMAGE}:${DOCKER_TAG}")
                        appImage.**push**()
                        **appImage.push("latest")**
                    }
                }
            }
        }
    }
    post {
        success {
            echo "Docker image ${DOCKER_IMAGE}:${DOCKER_TAG} has been built and pushed successfully!"
        }
        failure {
            echo "Pipeline failed. Please check the logs."
        }
    }
}

이 파이프라인은 Git에서 코드를 가져와서, 버전을 확인하고, Docker 이미지를 빌드한 후 Docker Hub에 푸시하는 자동화된 CI/CD 프로세스를 구현하는 코드이다.

코드 파이프라인이 성공적으로 종료된 후 docker hub에서도 새로운 tag(0.0.3)이 올라간 것을 확인할 수 있다.

Jenkins CI/CD + K8S(Kind)

[[K8S CD 실습] Jenkins 를 이용한 blue-green 배포 준비]

# 
cd dev-app

#
mkdir deploy

#
cat > deploy/echo-server-blue.yaml <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: echo-server-blue
spec:
  replicas: 2
  selector:
    matchLabels:
      app: echo-server
      version: blue
  template:
    metadata:
      labels:
        app: echo-server
        version: blue
    spec:
      containers:
      - name: echo-server
        image: hashicorp/http-echo
        args:
        - "-text=Hello from Blue"
        ports:
        - containerPort: 5678
EOF

cat > deploy/echo-server-service.yaml <<EOF
apiVersion: v1
kind: Service
metadata:
  name: echo-server-service
spec:
  selector:
    app: echo-server
    version: blue
  ports:
  - protocol: TCP
    port: 80
    targetPort: 5678
    nodePort: 30000
  type: NodePort
EOF

cat > deploy/echo-server-green.yaml <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: echo-server-green
spec:
  replicas: 2
  selector:
    matchLabels:
      app: echo-server
      version: green
  template:
    metadata:
      labels:
        app: echo-server
        version: green
    spec:
      containers:
      - name: echo-server
        image: hashicorp/http-echo
        args:
        - "-text=Hello from Green"
        ports:
        - containerPort: 5678
EOF

#
tree
git add . && git commit -m "Add echo server yaml" && git push -u origin main

그 뒤에는 Jenkins pipeline을 추가한다.

pipeline {
    agent any

    environment {
        KUBECONFIG = credentials('k8s-crd')
    }

    stages {
        stage('Checkout') {
            steps {
                 git branch: 'main',
                 url: 'http://***<자신의  IP>***:3000/devops/dev-app.git',  // Git에서 코드 체크아웃
                 credentialsId: 'gogs-crd'  // Credentials ID
            }
        }

        stage('container image build') {
            steps {
                echo "container image build"
            }
        }

        stage('container image upload') {
            steps {
                echo "container image upload"
            }
        }

        stage('k8s deployment blue version') {
            steps {
                sh "kubectl apply -f ./deploy/echo-server-blue.yaml --kubeconfig $KUBECONFIG"
                sh "kubectl apply -f ./deploy/echo-server-service.yaml --kubeconfig $KUBECONFIG"
            }
        }

        stage('approve green version') {
            steps {
                input message: 'approve green version', ok: "Yes"
            }
        }

        stage('k8s deployment green version') {
            steps {
	        	sh "kubectl apply -f ./deploy/echo-server-green.yaml --kubeconfig $KUBECONFIG"
            }
        }

        stage('approve version switching') {
            steps {
                script {
                    returnValue = input message: 'Green switching?', ok: "Yes", parameters: [booleanParam(defaultValue: true, name: 'IS_SWITCHED')]
                    if (returnValue) {
                        sh "kubectl patch svc echo-server-service -p '{\"spec\": {\"selector\": {\"version\": \"green\"}}}' --kubeconfig $KUBECONFIG"
                    }
                }
            }
        }

        stage('Blue Rollback') {
            steps {
                script {
                    returnValue = input message: 'Blue Rollback?', parameters: [choice(choices: ['done', 'rollback'], name: 'IS_ROLLBACk')]
                    if (returnValue == "done") {
                        sh "kubectl delete -f ./deploy/echo-server-blue.yaml --kubeconfig $KUBECONFIG"
                    }
                    if (returnValue == "rollback") {
                        sh "kubectl patch svc echo-server-service -p '{\"spec\": {\"selector\": {\"version\": \"blue\"}}}' --kubeconfig $KUBECONFIG"
                    }
                }
            }
        }
    }
}

pipeline {
    agent any

    environment {
        KUBECONFIG = credentials('k8s-crd')
    }

    stages {
        stage('Checkout') {
            steps {
                 git branch: 'main',
                 url: 'http://192.168.254.127:3000/devops/dev-app.git',  // Git에서 코드 체크아웃
                 credentialsId: 'gogs-crd'  // Credentials ID
            }
        }

        stage('container image build') {
            steps {
                echo "container image build"
            }
        }

        stage('container image upload') {
            steps {
                echo "container image upload"
            }
        }

        stage('k8s deployment blue version') {
            steps {
                sh "kubectl apply -f ./deploy/echo-server-blue.yaml --kubeconfig $KUBECONFIG"
                sh "kubectl apply -f ./deploy/echo-server-service.yaml --kubeconfig $KUBECONFIG"
            }
        }

        stage('approve green version') {
            steps {
                input message: 'approve green version', ok: "Yes"
            }
        }

        stage('k8s deployment green version') {
            steps {
	        	sh "kubectl apply -f ./deploy/echo-server-green.yaml --kubeconfig $KUBECONFIG"
            }
        }

        stage('approve version switching') {
            steps {
                script {
                    returnValue = input message: 'Green switching?', ok: "Yes", parameters: [booleanParam(defaultValue: true, name: 'IS_SWITCHED')]
                    if (returnValue) {
                        sh "kubectl patch svc echo-server-service -p '{\"spec\": {\"selector\": {\"version\": \"green\"}}}' --kubeconfig $KUBECONFIG"
                    }
                }
            }
        }

        stage('Blue Rollback') {
            steps {
                script {
                    returnValue = input message: 'Blue Rollback?', parameters: [choice(choices: ['done', 'rollback'], name: 'IS_ROLLBACk')]
                    if (returnValue == "done") {
                        sh "kubectl delete -f ./deploy/echo-server-blue.yaml --kubeconfig $KUBECONFIG"
                    }
                    if (returnValue == "rollback") {
                        sh "kubectl patch svc echo-server-service -p '{\"spec\": {\"selector\": {\"version\": \"blue\"}}}' --kubeconfig $KUBECONFIG"
                    }
                }
            }
        }
    }
}

이 Jenkins 파이프라인을 통해 쿠버네티스에 블루 버전의 애플리케이션을 먼저 배포한 뒤, 그린 버전을 배포하고, 추가 승인을 통해 서비스의 트래픽을 그린 버전으로 전환하게 된다.

문제가 없다면 이후 프로세스로 블루 버전을 삭제하거나 문제 발생 시 블루 버전으로 롤백(Blue Rollback)할 수 있는 선택권을 제공한다.

kubectl delete deploy echo-server-blue echo-server-green && kubectl delete svc echo-server-service

생성했던 리소스를 삭제한다.

Argo CD + K8S(Kind)

[Argo CD 소개 : Argo CD is a declarative, GitOps continuous delivery tool for Kubernetes]

Argo CD는 Git 저장소에 정의된 애플리케이션 상태를 쿠버네티스 클러스터에 자동으로 동기화하는 GitOps 기반 지속적 배포 도구이다. 다양한 형식의 쿠버네티스 매니페스트를 지원하며, 선언적이고 버전 관리되는 애플리케이션 정의를 통해 안정적이고 감사 가능한 배포 프로세스를 제공한다.

[핵심 원칙]

  • 선언적이고 버전 관리되는 애플리케이션 정의
  • 애플리케이션 정의, 설정 및 환경은 선언적으로 작성되어야 함
  • 모든 설정은 버전 관리 시스템에서 관리되어야 함
  • 자동화된 애플리케이션 배포 및 라이프사이클 관리
  • 배포 과정은 자동화되어야 함
  • 감사 가능하고 이해하기 쉬운 방식으로 관리되어야 함

[작동 방식]
Git 저장소를 원하는 애플리케이션 상태의 '단일 진실 소스(source of truth)'로 사용
실제 클러스터 상태가 Git에 정의된 상태와 일치하도록 지속적으로 동기화 (==GitOps)

[Argo CD 설치 및 기본 설정]

# 네임스페이스 생성 및 파라미터 파일 작성
cd cicd-labs

kubectl create ns argocd
cat <<EOF > argocd-values.yaml
dex:
  enabled: false

server:
  service:
    type: NodePort
    nodePortHttps: 30002
  extraArgs:
    - --insecure  # HTTPS 대신 HTTP 사용
EOF

# 설치
helm repo add argo https://argoproj.github.io/argo-helm
helm install argocd argo/argo-cd --version 7.8.13 -f argocd-values.yaml --namespace argocd # 7.7.10

# 확인
kubectl get pod,svc,ep,secret,cm -n argocd
kubectl get crd | grep argo
applications.argoproj.io                     2024-04-14T08:12:16Z
applicationsets.argoproj.io                  2024-04-14T08:12:17Z
appprojects.argoproj.io                      2024-04-14T08:12:16Z

kubectl get appproject -n argocd -o yaml

# configmap
kubectl get cm -n argocd argocd-cm -o yaml
kubectl get cm -n argocd argocd-rbac-cm -o yaml
...
data:
  policy.csv: ""
  policy.default: ""
  policy.matchMode: glob
  scopes: '[groups]'


# 최초 접속 암호 확인
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d ;echo
XxJMMJUv8MHZa-kk

# Argo CD 웹 접속 주소 확인 : 초기 암호 입력 (admin 계정)
open "http://127.0.0.1:30002" # macOS
# Windows OS경우 직접 웹 브라우저에서 http://127.0.0.1:30002 접속

[helm chart 를 통한 배포 실습]

#
cd cicd-labs
mkdir nginx-chart
cd nginx-chart

mkdir templates

cat > templates/configmap.yaml <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}
data:
  index.html: |
{{ .Values.indexHtml | indent 4 }}
EOF

cat > templates/deployment.yaml <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Release.Name }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app: {{ .Release.Name }}
  template:
    metadata:
      labels:
        app: {{ .Release.Name }}
    spec:
      containers:
      - name: nginx
        image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
        ports:
        - containerPort: 80
        volumeMounts:
        - name: index-html
          mountPath: /usr/share/nginx/html/index.html
          subPath: index.html
      volumes:
      - name: index-html
        configMap:
          name: {{ .Release.Name }}
EOF

cat > templates/service.yaml <<EOF
apiVersion: v1
kind: Service
metadata:
  name: {{ .Release.Name }}
spec:
  selector:
    app: {{ .Release.Name }}
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
    nodePort: 30000
  type: NodePort
EOF

cat > values.yaml <<EOF
indexHtml: |
  <!DOCTYPE html>
  <html>
  <head>
    <title>Welcome to Nginx!</title>
  </head>
  <body>
    <h1>Hello, Kubernetes!</h1>
    <p>Nginx version 1.26.1</p>
  </body>
  </html>

image:
  repository: nginx
  tag: 1.26.1

replicaCount: 1
EOF

cat > Chart.yaml <<EOF
apiVersion: v2
name: nginx-chart
description: A Helm chart for deploying Nginx with custom index.html
type: application
version: 1.0.0
appVersion: "1.26.1"
EOF

# 이전 timeserver/service(nodeport) 삭제
kubectl delete deploy,svc --all

# 직접 배포 해보기
helm template dev-nginx . -f values.yaml
helm install dev-nginx . -f values.yaml
helm list
kubectl get deploy,svc,ep,cm dev-nginx -owide

#
curl http://127.0.0.1:30000
curl -s http://127.0.0.1:30000 | grep version
open http://127.0.0.1:30000


# value 값 변경 후 적용 해보기 : version/tag, replicaCount
cat > values.yaml <<EOF
indexHtml: |
  <!DOCTYPE html>
  <html>
  <head>
    <title>Welcome to Nginx!</title>
  </head>
  <body>
    <h1>Hello, Kubernetes!</h1>
    <p>Nginx version 1.26.2</p>
  </body>
  </html>

image:
  repository: nginx
  tag: 1.26.2

replicaCount: 2
EOF

sed -i '' "s|1.26.1|1.26.2|g" Chart.yaml


# helm chart 업그레이드 적용
helm template dev-nginx . -f values.yaml # 적용 전 렌더링 확인 Render chart templates locally and display the output.
helm upgrade dev-nginx . -f values.yaml

# 확인
helm list
kubectl get deploy,svc,ep,cm dev-nginx -owide
curl http://127.0.0.1:30000
curl -s http://127.0.0.1:30000 | grep version
open http://127.0.0.1:30000

# 확인 후 삭제
helm uninstall dev-nginx

[Repo(ops-deploy) 에 nginx helm chart 를 Argo CD를 통한 배포 1]

cd cicd-labs

TOKEN=<>
git clone http://devops:$TOKEN@$MyIP:3000/devops/ops-deploy.git
cd ops-deploy

#
git --no-pager config --local --list
git config --local user.name "devops"
git config --local user.email "a@a.com"
git config --local init.defaultBranch main
git config --local credential.helper store
git --no-pager config --local --list
cat .git/config

#
git --no-pager branch
git remote -v

#
VERSION=1.26.1
mkdir nginx-chart
mkdir nginx-chart/templates

cat > nginx-chart/VERSION <<EOF
$VERSION
EOF

cat > nginx-chart/templates/configmap.yaml <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}
data:
  index.html: |
{{ .Values.indexHtml | indent 4 }}
EOF

cat > nginx-chart/templates/deployment.yaml <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Release.Name }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app: {{ .Release.Name }}
  template:
    metadata:
      labels:
        app: {{ .Release.Name }}
    spec:
      containers:
      - name: nginx
        image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
        ports:
        - containerPort: 80
        volumeMounts:
        - name: index-html
          mountPath: /usr/share/nginx/html/index.html
          subPath: index.html
      volumes:
      - name: index-html
        configMap:
          name: {{ .Release.Name }}
EOF

cat > nginx-chart/templates/service.yaml <<EOF
apiVersion: v1
kind: Service
metadata:
  name: {{ .Release.Name }}
spec:
  selector:
    app: {{ .Release.Name }}
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
    nodePort: 30000
  type: NodePort
EOF

cat > nginx-chart/values-dev.yaml <<EOF
indexHtml: |
  <!DOCTYPE html>
  <html>
  <head>
    <title>Welcome to Nginx!</title>
  </head>
  <body>
    <h1>Hello, Kubernetes!</h1>
    <p>DEV : Nginx version $VERSION</p>
  </body>
  </html>

image:
  repository: nginx
  tag: $VERSION

replicaCount: 1
EOF

cat > nginx-chart/values-prd.yaml <<EOF
indexHtml: |
  <!DOCTYPE html>
  <html>
  <head>
    <title>Welcome to Nginx!</title>
  </head>
  <body>
    <h1>Hello, Kubernetes!</h1>
    <p>PRD : Nginx version $VERSION</p>
  </body>
  </html>

image:
  repository: nginx
  tag: $VERSION

replicaCount: 2
EOF

cat > nginx-chart/Chart.yaml <<EOF
apiVersion: v2
name: nginx-chart
description: A Helm chart for deploying Nginx with custom index.html
type: application
version: 1.0.0
appVersion: "$VERSION"
EOF


tree nginx-chart
nginx-chart
├── Chart.yaml
├── VERSION
├── templates
│   ├── configmap.yaml
│   ├── deployment.yaml
│   └── service.yaml
├── values-dev.yaml
└── values-prd.yaml

# 
helm template dev-nginx nginx-chart -f nginx-chart/values-dev.yaml
helm template prd-nginx nginx-chart -f nginx-chart/values-prd.yaml
DEVNGINX=$(helm template dev-nginx nginx-chart -f nginx-chart/values-dev.yaml | sed 's/---//g')
PRDNGINX=$(helm template prd-nginx nginx-chart -f nginx-chart/values-prd.yaml | sed 's/---//g')
diff <(echo "$DEVNGINX") <(echo "$PRDNGINX")

#
git add . && git commit -m "Add nginx helm chart" && git push -u origin main

처음에 생성하게 되면 OutOfSync 상태로 나오게 된다. OutOfSync 상태는 Git 저장소에 정의된 원하는 상태와 쿠버네티스 클러스터의 실제 상태 간에 차이가 있다는 의미로, Sync 버튼을 클릭해 동기화해준다.

Sync가 완료되면 상태가 Synced 상태로 바뀌면서 App Health 상태도 Healthy한 상태로 변경된다.

? 만약 GitOps 방식을 무시하고 K8S(Live)를 수정 시도해보면 어떻게 될까? =>

Git에서 변경한 내용은 Diff로 잡히게 된다. 그러나 직접 파일을 수정한 후 Sync를 강제로 하게 되면 Diff로 잡히지 않기 때문에 굉장히 위험하다. ⇒ GitOps를 위해서는, 반드시 단일 진실 공급원(Single Source Of Trush, SSOT)를 통해서 관리해야 한다.

테스트하고 난 이후에는 Application을 삭제한다.

[Repo(ops-deploy) 에 nginx helm chart 를 Argo CD를 통한 배포 2 : ArgoCD Declarative Setup]

#
echo $MyIP

cat <<EOF | kubectl apply -f -
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: dev-nginx
  namespace: argocd
  finalizers:
  - resources-finalizer.argocd.argoproj.io
spec:
  project: default
  source:
    helm:
      valueFiles:
      - values-dev.yaml
    path: nginx-chart
    repoURL: http://$MyIP:3000/devops/ops-deploy
    targetRevision: HEAD
  syncPolicy:
    automated:
      prune: true
    syncOptions:
    - CreateNamespace=true
  destination:
    namespace: dev-nginx
    server: https://kubernetes.default.svc
EOF


#
kubectl get applications -n argocd dev-nginx
kubectl get applications -n argocd dev-nginx -o yaml | kubectl neat
kubectl describe applications -n argocd dev-nginx
kubectl get pod,svc,ep,cm -n dev-nginx

#
curl http://127.0.0.1:30000
open http://127.0.0.1:30000

# Argo CD App 삭제
kubectl delete applications -n argocd dev-nginx

ArgoCD의 자동 동기화(Auto Sync)는 Git 저장소의 변경 사항을 감지하여 쿠버네티스 클러스터에 자동으로 적용한다.

How often does Argo CD check for changes to my Git or Helm repository ? => The default polling interval is 3 minutes (180 seconds). You can change the setting by updating the timeout.reconciliation value in the argocd-cm config map

위에서 설정했던 것처럼 Repo(ops-deploy) 에 Webhook 를 통해 Argo CD 에 즉시 반영 trigger하여 k8s 배포 할 수 있게 설정할 수도 있다.

kubectl delete applications -n argocd dev-nginx

Argo Image Updater

Argocd 기존 방식

ArgoCD Image Updater

Argo CD App-of-apps

Argocd 기존 방식

App of Apps

하나의 "부모" ArgoCD 애플리케이션이 여러 "자식" ArgoCD 애플리케이션을 정의하고 관리하는 방식이다. 이를 통해 많은 애플리케이션을 구조화되고 일관된 방식으로 관리할 수 있다.

├── apps/                     # 상위 애플리케이션 디렉터리
│   ├── Chart.yaml           # Helm 차트 정의 (선택사항)
│   ├── values.yaml          # 공통 값 정의 (선택사항)
│   └── templates/           # 애플리케이션 템플릿
│       ├── app1.yaml        # 앱1 ArgoCD Application 정의
│       ├── app2.yaml        # 앱2 ArgoCD Application 정의
│       └── ...              # 추가 애플리케이션
├── app1/                     # 앱1 매니페스트 디렉터리
├── app2/                     # 앱2 매니페스트 디렉터리
└── ...                       # 추가 앱 디렉터리
#
cd cicd-labs
git clone https://github.com/argoproj/argocd-example-apps.git

#
tree argocd-example-apps/apps
argocd-example-apps/apps
├── Chart.yaml
├── templates
│   ├── helm-guestbook.yaml
│   ├── helm-hooks.yaml
│   ├── kustomize-guestbook.yaml
│   ├── namespaces.yaml
│   └── sync-waves.yaml
└── values.yaml

#
helm template -f argocd-example-apps/apps/values.yaml argocd-example-apps/apps


# you need to create and sync your parent app
cat <<EOF | kubectl apply -f -
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: apps
  namespace: argocd
  finalizers:
  - resources-finalizer.argocd.argoproj.io
spec:
  project: default
  source:
    path: apps
    repoURL: https://github.com/argoproj/argocd-example-apps.git
    targetRevision: HEAD
  destination:
    namespace: argocd
    server: https://kubernetes.default.svc
  syncPolicy:
    automated:
      prune: true
EOF

#
kubectl get applications -n argocd --show-labels 
NAME                  SYNC STATUS   HEALTH STATUS   LABELS
apps                  Synced        Healthy         <none>
helm-guestbook        OutOfSync     Missing         argocd.argoproj.io/instance=apps
helm-hooks            OutOfSync     Missing         argocd.argoproj.io/instance=apps
kustomize-guestbook   OutOfSync     Missing         argocd.argoproj.io/instance=apps
sync-waves            OutOfSync     Missing         argocd.argoproj.io/instance=apps

# 상태 모니터링
kubectl get applications -n argocd -w

apps라는 "부모" ArgoCD 애플리케이션이 여러 "자식" ArgoCD 애플리케이션(helm-guestbook, helm-hooks...)을 정의하고 관리하는 것을 확인할 수 있다.

Argo Rollout

Argo Rollouts는 Ingress Controller 및 Service Meshes와 통합되어 트래픽 형성 기능을 활용하여 업데이트 중에 트래픽을 점진적으로 새 버전으로 전환한다. 또한 Rollouts는 다양한 공급업체의 메트릭을 쿼리하고 해석하여 주요 KPI를 검증하고 업데이트 중에 자동화된 프로모션 또는 롤백을 실행할 수 있다.

https://argoproj.github.io/argo-rollouts/

profile
공블로그

0개의 댓글

관련 채용 정보