WellSpring

1주차 - Jenkins CI/CD + Docker 본문

CICD

1주차 - Jenkins CI/CD + Docker

daniel00324 2024. 12. 1. 23:18

※ 본 게재 글은 gasida님의 '약식 CI/CD' 강의내용과 실습예제 및 Jenkins CI/CD, Docker 관련 Blog 등을 참고하여 작성하였습니다.


0. 실습환경 구성

☞ WSL2 + Docker Desktop , VSCODE (확장 : WSL)

 

1) WSL 2 설치 + Ubuntu 배포판 설치 - Docs , 설명서

 - Link : WSL2 설치하기

☞ Powershell 관리자 권한으로 실행

더보기
# DISM(배포 이미지 서비스 및 관리) 명령어로 Microsoft-Windows-Subsystem-Linux 기능을 활성화
dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart

# DISM 명령어로 VirtualMachinePlatform 기능을 활성화
dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart

# wsl 설치
wsl --install

# 기본값 WSL 2 버전 설정
wsl --set-default-version 2

# wsl 업데이트
wsl --update

 

  윈도우 OS 재부팅

  Powershell 관리자 권한으로 실행

더보기
# 설치 가능한 배포판 확인
wsl --list --online

# Ubuntu 배포판 설치
wsl --install Ubuntu-24.04
...
Enter new UNIX username: <각자 Ubuntu 사용 계정>
New password: <해당 계정 암호>
Retype new password: <해당 계정 암호>
passwd: password updated successfully
Installation successful!
To run a command as administrator (user "root"), use "sudo <command>".
---------------------------------------
# 기본 정보 확인
hostnamectl
whoami
id
pwd

# apt 업데이트
sudo apt update 
sudo apt install jq htop curl wget ca-certificates net-tools -y
ifconfig eth0
ping -c 1 8.8.8.8

# 빠져나오기
$ exit
---------------------------------------

# 설치된 배포판 확인
wsl -l -v

# Ubuntu 재진입
wsl

 

[ 실행결과 - 한 눈에 보기 ]

2) Docker Desktop for Windows 설치

Docker Desktop for Windows를 이용하는 것을 추천

(1) Docker 실치하기 - WSL 시작하기 : Blog

(2) Windows Docker 설치 완벽 가이드 : Blog

[ 필수 체크 항목 ]

☞ 설치 후 확인 : 관리자 권한으로 Powershell 열기

더보기
#
wsl -l -v

#
docker info
docker ps

 

[ 실습 결과 - 한 눈에 보기 ]

 

3) VSCODE 설치 - Download , WSL-VSCODE

  WSL 배포에서 프로젝트를 열려면 배포의 명령줄을 열고 다음을 입력합니다.

더보기
code .
Installing VS Code Server for Linux x64 (f1a4fb101478ce6ec82fe9627c43efbf9e98c813)
Downloading:   100%
Unpacking: 100%
Unpacked 1773 files and folders to /home/gasida/.vscode-server/bin/f1a4fb101478ce6ec82fe9627c43efbf9e98c813.
Looking for compatibility check script at /home/gasida/.vscode-server/bin/f1a4fb101478ce6ec82fe9627c43efbf9e98c813/bin/helpers/check-requirements.sh
Running compatibility check script
Compatibility check successful (0)

 

[ 도전과제 1 ]

  • Remote Development Even Better - Blog
  • Developing inside a Container - Docs
  • Using Dev Containers in WSL 2 - Docs
더보기

[  VSCODE : python extention 설치 ] - Link


1. 컨테이너를 활용한 애플리케이션 개발

실습 시나리오 참고 : https://github.com/WilliamDenniss/kubernetes-for-developers Chapter02

1.1 python 으로 특정 문자열 출력

Windows : 경우 WSL Ubuntu 내부에서 아래 작업 진행

더보기
# 코드 작성
mkdir 1.1 && cd 1.1

echo "print ('Hello Docker')" > hello.py

cat > Dockerfile <<EOF
FROM python:3
COPY . /app
WORKDIR /app 
CMD python3 hello.py
EOF

# 컨테이너 이미지 빌드
docker pull python:3
docker build . -t hello
docker image ls -f reference=hello

# 컨테이너 실행
docker run --rm hello
  •   코드 수정
# 코드 수정
echo "print ('Hello CloudNet@')" > hello.py

# 컨테이너 이미지 빌드 : latest 활용 해보자!
docker build . -t hello:1
docker image ls -f reference=hello
docker tag hello:1 hello:latest
docker image ls -f reference=hello

# 컨테이너 실행
docker run --rm hello:1
docker run --rm hello

  

[ 실행 결과 - 한 눈에 보기 ] 

1.2 Compiling code in Docker

더보기
# 코드 작성
mkdir 1.2 && cd 1.2

cat > Hello.java <<EOF
class Hello {
    public static void main(String[] args) {
        System.out.println("Hello Docker");
    }
}
EOF

cat > Dockerfile <<EOF
FROM openjdk
COPY . /app
WORKDIR /app
RUN javac Hello.java    # The complie command
CMD java Hello
EOF

# 컨테이너 이미지 빌드
docker pull openjdk
docker build . -t hello:2
docker tag hello:2 hello:latest
docker image ls -f reference=hello

# 컨테이너 실행
docker run --rm hello:2
docker run --rm hello

# 컨테이너 이미지 내부에 파일 목록을 보면 어떤가요? 꼭 필요한 파일만 있는가요? 보안적으로 어떨까요?
docker run --rm hello ls -l

# RUN 컴파일 시 소스코드와 java 컴파일러(javac)가 포함되어 있음. 실제 애플리케이션 실행에 필요 없음. 
docker run --rm hello javac --help
docker run --rm hello ls -l

 

[ 실행 결과 - 한 눈에 보기 ]

 

☞ 고민해 보아야 할 점!!

더보기
  • 로컬에서 애플리케이션을 미리 컴파일 하고 추가 할 수 있고, 실습 예시처럼 Dockerfile 에서 직접 컴파일 할 수 도 있다.
  • 어떤 방법이 각자 회사에 개발자분들이 선호하는지, 그리고 CI/CD 전체 과정에서 보면 어떤 방법이 적합한지, 보안적으로 문제는 없는지 고민 필요!!

1.3 Compiling code with a multistage build

 : 최종 빌드 전에 컴파일 등 실행하는 임시 컨테이너를 사용

☞  최종 서비스 컨테이너에 필요에 따른 최소한의 파일만 전달하여, 보안 취약점을 개선하자!! 

 

[ 실습 코드 ]  

더보기
# 코드 작성
mkdir 1.3 && cd 1.3

cat > Hello.java <<EOF
class Hello {
    public static void main(String[] args) {
        System.out.println("Hello Multistage container build");
    }
}
EOF

cat > Dockerfile <<EOF
FROM openjdk:11 AS buildstage
COPY . /app
WORKDIR /app
RUN javac Hello.java

FROM openjdk:11-jre-slim
COPY --from=buildstage /app/Hello.class /app/
WORKDIR /app
CMD java Hello
EOF

# 컨테이너 이미지 빌드 : 용량 비교 해보자!
docker build . -t hello:3
docker tag hello:3 hello:latest
docker image ls -f reference=hello

# 컨테이너 실행
docker run --rm hello:3
docker run --rm hello

# 컨테이너 이미지 내부에 파일 목록을 보면 어떤가요?
docker run --rm hello ls -l
docker run --rm hello javac --help

 

[ 실행 결과 - 한 눈에 보기 ] * 1.2 와 비교

 

1.4 Jib로 자바 컨테이너 빌드 - Docs , Blog1 , Blog2

☞ Jib를 통한 Build 프로세스 간소화가 가능하다!!

JIB(Java Image Builder)는 Google이 개발한 Java Application Container Image Builder 이다.

JIB는 Dockerfile 이나 Docker 데몬 없이 Java Application의 Docker 및 OCI container 이미지를 생성할 수 있으며, MavenGradle 플러그인의 형태로 제공된다.

 

[ Docker Build Flow ]

☞ 도커 파일 작성 → 이미지 빌드 → 저장소에 이미지 푸시

 

[ JIB Build Flow ]

☞ 프로젝트에서 빌드와 동시에 이미지 만들어지고 저장소에 푸시까지 처리 ( 개발자가 편리하다!! )

 

[ More ... ]

더보기

<< JIB의 장점 >>

 

1. 간편함(Simple and Demonless)

  - Java로 구현 되며 Maven or Gradle Build의 일부로 실행함
  - DockerFile 유지, Docker 데몬 실행, Fat 한 Jar를 Build 할 걱정을 할 필요가 없다!!

 

2. 속도 (Fast)

  - Jib은 Image layering , Registry caching 를 활용하여 빠르게 빌드함
  - Image layering : dependencies, resources, classes로 layer 나누고 변경 부분만 Rebuild 

 

3. Reproducible

  - Jib으로 생성된 컨테이너 이미지는 동일한 콘텐츠로 다시 빌드할 경우 항상 동일한 결과를 생성한다!!

1.5 Containerizing an application server

☞ 이제, Application 서버 구성을 통해 좀 더 실제 서비스 상황에 근접한 실습을 해 보자!!

  • To demonstrate how to containerize HTTP servers, we’ll need something that is an HTTP server!
  • An example of a bare-bones HTTP server in Python that returns the current date and time.
더보기
# 코드 작성
mkdir 1.5 && cd 1.5

cat > server.py <<EOF
from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler
from datetime import datetime

class RequestHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header('Content-type', 'text/plain')
        self.end_headers()
        now = datetime.now()
        response_string = now.strftime("The time is %-I:%M %p, UTC.\n")
        self.wfile.write(bytes(response_string, "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

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

# 컨테이너 이미지 빌드 : 용량 비교 해보자!
docker pull python:3.12
docker build . -t timeserver:1 && docker tag timeserver:1 timeserver:latest
docker image ls -f reference=timeserver

# 컨테이너 실행
docker run -d -p 8080:80 --name=timeserver timeserver

# 컨테이너 접속 및 로그 확인
curl http://localhost:8080
docker logs timeserver

# 컨테이너 이미지 내부에 파일 확인
docker exec -it timeserver ls -l

# 컨테이너 이미지 내부에 server.py 파일 수정 후 반영 확인 : VSCODE 경우 docker 확장프로그램 활용
docker exec -it timeserver cat server.py

# 컨테이너 접속 후 확인 
curl http://localhost:8080

# 컨테이너 삭제
docker rm -f timeserver

 

[ 실행 결과 - 한 눈에 보기 ]  * 이미지 수정 후, 반영을 위해 Rebuild 과정이 필요!!

 

1.6 Using Docker Compose for local testing

[ Docker Compose 란? ]

 - 단일 서비스에서 여러 개의 컨테이너를 하나의 서비스로 정의하여 한 번에 여러 개의 컨테이너를 관리 할 수 있는 환경을 제공해 주는 관리 Tool 이다.

 

[ Docker Compose 의 특징 ]

 - 다수의 컨테이너와 복잡한 옵션의 설정이 필요한 환경의 관리에 유용하다. 

더보기

♣ 간소화된 제어: Docker Compose를 사용하면 단일 YAML 파일에서 다중 컨테이너 애플리케이션을 정의하고 관리할 수 있습니다. 이를 통해 다양한 서비스를 조정하고 조정하는 복잡한 작업이 간소화되어 애플리케이션 환경을 관리하고 복제하기가 더 쉬워집니다.


♣ 효율적인 협업: Docker Compose 구성 파일은 공유하기 쉽고, 개발자, 운영팀 및 기타 이해 관계자 간의 협업을 용이하게 합니다. 이러한 협업적 접근 방식은 더 원활한 워크플로, 더 빠른 문제 해결 및 전반적인 효율성 증가로 이어집니다.

♣ 빠른 애플리케이션 개발: Compose는 컨테이너를 만드는 데 사용된 구성을 캐시합니다. 변경되지 않은 서비스를 다시 시작하면 Compose는 기존 컨테이너를 재사용합니다. 컨테이너를 재사용하면 환경을 매우 빠르게 변경할 수 있습니다.

♣ 환경 간 이식성: Compose는 Compose 파일에서 변수를 지원합니다. 이러한 변수를 사용하여 다양한 환경 또는 다양한 사용자에 맞게 구성을 사용자 지정할 수 있습니다.

♣ 광범위한 커뮤니티와 지원: Docker Compose는 활기차고 활동적인 커뮤니티의 혜택을 누리며, 이는 풍부한 리소스, 튜토리얼 및 지원을 의미합니다. 이 커뮤니티 중심 생태계는 Docker Compose의 지속적인 개선에 기여하고 사용자가 문제를 효과적으로 해결할 수 있도록 도와줍니다.

 

- Docker Compose는 여러 개의 컨테이너 옵션과 환경을 정의한 파일을 읽어 컨테이너를 순차적으로 생성함

- Docker 엔진의 run 명령어의 옵션을 그대로 사용 할 수 있다.

- Config File에 각 컨테이너의 의존성, 네트워크, 볼륨 등 을 정의할 수 있다.

- 스웜모드와 유사하게 하나의 Config File을 통해 정의된 서비스의 컨테이너 수를 유동적으로 조절 가능함

- 컨테이너 서비스의 Discovery 가 자동으로 이루어짐 

[ Docker Compose - Options ]

더보기
 Option                          Default  Description  
--all-resources   Include all resources, even those not used by services
--ansi auto Control when to print ANSI control characters ("never"|"always"|"auto")
--compatibility   Run compose in backward compatibility mode
--dry-run   Execute command in dry run mode
--env-file   Specify an alternate environment file
-f, --file   Compose configuration files
--parallel -1 Control max parallelism, -1 for unlimited
--profile   Specify a profile to enable
--progress auto Set type of progress output (auto, tty, plain, json, quiet)
--project-directory   Specify an alternate working directory
(default: the path of the, first specified, Compose file)
-p, --project-name   Project name

 

 

☞ local 호스트 Disk 의 볼륨마운트와 Python "reloading" 기능을 통해 좀 더 쉽게 변경 사항을 반영해보자!! 

더보기
#
# 코드 작성
mkdir 1.6 && cd 1.6

cat > server.py <<EOF
from reloading import reloading
from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler
from datetime import datetime

class RequestHandler(BaseHTTPRequestHandler):
    @reloading   # By adding the @reloading tag to our method, it will be reloaded from disk every time it runs so we can change our do_GET function while it’s running.
    def do_GET(self):
        self.send_response(200)
        self.send_header('Content-type', 'text/plain')
        self.end_headers()
        now = datetime.now()
        response_string = now.strftime("The time is %H:%M:%S, Docker End.")
        self.wfile.write(bytes(response_string,"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

# reloading 라이브러리 설치 필요
cat > Dockerfile <<EOF
FROM python:3
RUN pip install reloading
ENV PYTHONUNBUFFERED 1
COPY . /app
WORKDIR /app 
CMD python3 server.py
EOF

cat > docker-compose.yaml <<EOF
services:
  frontend:
    build: .
    command: python3 server.py
    volumes:
      - type: bind
        source: .
        target: /app
    environment:
      PYTHONDONTWRITEBYTECODE: 1  # Sets a new environment variable so that Python can be made to reload our source
    ports:
      - "8080:80"
EOF

#
docker compose build; docker compose up -d

# 컴포즈로 실행 시 이미지와 컨테이너 네이밍 규칙을 알아보자!
docker compose ps
docker compose images

#
curl http://localhost:8080
docker compose logs

 

☞ 코드 내용 변경 후 확인 : 호스트에서 server.py 코드 수정(볼륨 공유 상태) 후 curl 접속 시 자동 반영 확인

# VSCODE 에서 호스트에서 server.py 코드 수정(볼륨 공유 상태) 
cat server.py
...
        response_string = now.strftime("The time is %H:%M:%S, Docker EndEndEnd!")
        self.wfile.write(bytes(response_string,"utf-8")) 
...

#
curl http://localhost:8080

#
docker compose down

 

[ 실행 결과 - 한 눈에 보기 ] 


[ Compose - container Naming Rule 관련 ]

(심화 참고) Multi-platform - Link , Docs

더보기

☞ docker buildx 명령어를 통해 한번에 다양한 platform의 이미지 빌드가 가능하다!!

  • Build multi-platform images
    • When triggering a build, use the --platform flag to define the target platforms for the build output, such as linux/amd64 and linux/arm64:
docker buildx build --platform linux/amd64,linux/arm64 .
  • You can build multi-platform images using three different strategies, depending on your use case:
    1. Using emulation, via QEMU
      • Building multi-platform images under emulation with QEMU is the easiest way to get started if your builder already supports it. Using emulation requires no changes to your Dockerfile, and BuildKit automatically detects the architectures that are available for emulation.
        • Linux kernel version 4.8 or later
        • binfmt-support version 2.1.7 or later : apt install binfmt-support
        • The QEMU binaries must be statically compiled and registered with the fix_binary flag
  • Use the tonistiigi/binfmt image to install QEMU and register the executable types on the host with a single command:
docker run --privileged --rm tonistiigi/binfmt --install all
...
installing: mips64 OK
installing: arm OK
installing: s390x OK
installing: ppc64le OK
installing: mips64le OK
installing: arm64 OK
installing: riscv64 OK
{
  "supported": [
    "linux/amd64",
    "linux/arm64",
    "linux/riscv64",
    "linux/ppc64le",
    "linux/s390x",
    "linux/386",
    "linux/mips64le",
    "linux/mips64",
    "linux/arm/v7",
    "linux/arm/v6"
  ],
  "emulators": [
    "python3.10",
    "qemu-aarch64",
    "qemu-arm",
    "qemu-mips64",
    "qemu-mips64el",
    "qemu-ppc64le",
    "qemu-riscv64",
    "qemu-s390x"
  ]
}

#
ls /proc/sys/fs/binfmt_misc/qemu-*

 

2. Use a builder with multiple native nodes

#
docker buildx create --name builder --driver docker-container --use
docker buildx inspect --bootstrap

#
git clone https://github.com/spkane/wordchain.git --config core.autocrlf=input
cd wordchain

#
cat Dockerfile

#
docker buildx build --tag wordchain:test --load .
docker images

#
docker container run wordchain:test random

#
docker container run wordchain:test random -l 3 -d .

#
docker container run wordchain:test --help



# You can build this image for both the linux/amd64 and the linux/arm64 platforms like this:
docker buildx build --platform linux/amd64,linux/arm64 --push --tag gasida/myweb:multi .


# 
docker manifest inspect gasida/myweb:multi | jq

# 자신의 도커 허브에 이미지 업로드 후 도커 웹 에서 확인 >> 자신의 mac M에서 컨테이너 이미지 다운로드 후 기동 해보자!
docker container run gasida/myweb:multi random

 

3. Use cross-compilation with multi-stage builds

 

☞ 실습 따라하기

#
docker buildx create --name builder --driver docker-container --use
docker buildx inspect --bootstrap

#
git clone https://github.com/spkane/wordchain.git --config core.autocrlf=input
cd wordchain

#
cat Dockerfile

#
docker buildx build --tag wordchain:test --load .
docker images

#
docker container run wordchain:test random

#
docker container run wordchain:test random -l 3 -d .

#
docker container run wordchain:test --help

# You can build this image for both the linux/amd64 and the linux/arm64 platforms like this:
docker buildx build --platform linux/amd64,linux/arm64 --push --tag gasida/myweb:multi .

# 
docker manifest inspect gasida/myweb:multi | jq
   "manifests": [
      {
         "mediaType": "application/vnd.oci.image.manifest.v1+json",
         "size": 673,
         "digest": "sha256:a6e93cb9e2e1971802ee60536fddb5301affd7afc705ad63372aadd5c31c6848",
         "platform": {
            "architecture": "amd64",
            "os": "linux"
         }
      },
      {
         "mediaType": "application/vnd.oci.image.manifest.v1+json",
         "size": 673,
         "digest": "sha256:50617c29e7b7f02db80f5bba4bb836b47ba24071757953cb31591505b821a696",
         "platform": {
            "architecture": "arm64",
            "os": "linux"
         }
...

# 자신의 도커 허브에 이미지 업로드 후 도커 웹 에서 확인 >> 자신의 mac M에서 컨테이너 이미지 다운로드 후 기동 해보자!
docker container run gasida/myweb:multi random

 

 


2. CI/CD 실습환경 구성

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

 

1) Jenkins & Gogs 기동

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

# 
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

 

[ 실행 결과 - 한 눈에 보기 ]

2) Jenkins 컨테이너 초기 설정

더보기
# Jenkins 초기 암호 확인
docker compose exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword
09a21116f3ce4f27a0ede79372febfb1

# Jenkins 웹 접속 주소 확인 : 계정 / 암호 입력 >> admin / qwe123
open "http://127.0.0.1:8080" # macOS
웹 브라우저에서 http://127.0.0.1:8080 접속 # Windows

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

3) Jenkins URL 설정 : 각자 자신의 PC의 IP를 입력

더보기

 

▶ Windows 에서는 자신의 PC IP 확인 : cmd 창에서 ipconfig

 

[ 실행 결과 - 한 눈에 보기 ]

4) 기타 참고사항

더보기

(참고) 실습 완료 후 해당 컨테이너 중지 상태로 둘 경우 → 재부팅 및 이후에 다시 실습을 위해 컨테이너 시작 시

# 실습 완료 후 해당 컨테이너 중지 상태로 둘 경우
docker compose stop
docker compose ps
docker compose ps -a

# mac 재부팅 및 이후에 다시 실습을 위해 컨테이너 시작 시
docker compose start
docker compose ps

 

(참고) 특정 컨테이너만 삭제 후 다시 초기화 상태로 기동 시

# gogs : 볼륨까지 삭제
docker compose down gogs -v
docker compose up gogs -d

# jenkins : 볼륨까지 삭제
docker compose down jenkins -v
docker compose up jenkins -d

[ About Jenkins ]

Jenkins 는 소프트웨어 빌드, 테스트, 제공 또는 배포와 관련된 모든 종류의 작업을 자동화하는 데 사용할 수 있는 독립형 오픈 소스 자동화 서버입니다.
Jenkins는 기본 시스템 패키지, Docker를 통해 설치할 수 있으며, Java Runtime Environment(JRE)가 설치된 모든 시스템에서 단독으로 실행할 수도 있습니다.

 

Jenkins : The open source automation server, support building deploying and automating any project - DockerHub , Github , Docs

더보기

CI(지속적 제공)/CD(지속적 배포) 워크플로 예제 : Continuous Integration Server + Continuous Development, Build, Test, Deploy - Link

  1. 최신 코드 가져오기 : 개발을 위해 중앙 코드 리포지터리에서 로컬 시스템으로 애플리케이션의 최신 코드를 가져옴
  2. 단위 테스트 구현과 실행 : 코드 작성 전 단위 테스트 케이스를 먼저 작성
  3. 코드 개발 : 실패한 테스트 케이스를 성공으로 바꾸면서 코드 개발
  4. 단위 테스트 케이스 재실행 : 단위 테스트 케이스 실행 시 통과(성공!)
  5. 코드 푸시와 병합 : 개발 소스 코드를 중앙 리포지터리로 푸시하고, 코드 병합
  6. 코드 병합 후 컴파일 : 변경 함수 코드가 병함되면 전체 애플리케이션이 컴파일된다
  7. 병합된 코드에서 테스트 실행 : 개별 테스트뿐만 아니라 전체 통합 테스트를 실행하여 문제 없는지 확인
  8. 아티팩트 배포 : 애플리케이션을 빌드하고, 애플리케이션 서버의 프로덕션 환경에 배포
  9. 배포 애플리케이션의 E-E 테스트 실행 : 셀레늄 Selenium과 같은 User Interface 자동화 도구를 통해 애플리케이션의 전체 워크플로가 정상 동작하는지 확인하는 종단간 End-to-End 테스트를 실행
  • 소프트웨어 개발 프로세스의 다양한 단계자동화하는 도구로서 중앙 소스 코드 리포지터리에서 최신 코드 가져오기, 소스 코드 컴파일, 단위 테스트 실행, 산출물을 다양한 유형으로 패키징, 산출물을 여러 종류의 환경으로 배포하기 등의 기능을 제공.
  • 젠킨스는 아파치 톰캣처럼 서블릿 컨테이너 내부에서 실행되는 서버 시스템이다. 자바로 작성됐고, 소프트웨어 개발과 관련된 다양한 도구를 지원.
  • 젠킨스는 DSL Domain Specific Language (jenkins file)로 E-E 빌드 수명 주기 단계를 구축한다.
  • 젠킨스는 파이프라인이라고 부르는 스크립트를 작성할 수 있는데, 이를 사용해서 각 빌드 단계마다 젠킨스가 수행할 태스트 및 하위 태스크의 순서를 정의.
    • 순차적이고 종속적인 단계가 시작부터 끝까지 실행되면 최종적으로 사용자가 실행할 수 있는 빌드가 생성됨.
    • 만약 빌드 프로세스를 진행하는 중에 특정 단계에서 실패가 발생하며, 이 단계의 출력 결과를 사용하는 다음 단계는 실행되지 않으며 빌드 프로세스 전체가 실패한다.
  • 다양한 Plugins 연동
    • Build Plugins : Maven, Ant, Gradle …
    • VCS Plugins : Git, SVN …
    • Languages Plugins : Java, Python, Node.js …

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

더보기

[ DinD 와 DooD의 구조 비교 ]  

 

# 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 -y

docker info
docker ps
which docker

# Jenkins 컨테이너 내부에서 root가 아닌 jenkins 유저도 docker를 실행할 수 있도록 권한을 부여
groupadd -g 2000 -f docker
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
sudo docker compose restart jenkins  # Windows 경우 이후부터 sudo 붙여서 실행하자

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

 

[ 실행 결과 - 한 눈에 보기 ]

 

[ Tip & Tips ] 

☞ jenkins container 에서 docker group 권한 부여 후, WSL 에서 docker compose 명령어 수행 시 권한 에러 수정!!

더보기

문제의 원인은 local PC WSL 에서 지정된 docker  group ID 와 jenkins container 에서 지정한 group ID 가 달라서 발생하는 것이다.

따라서, 해결 방법은 WSL 과 jenkins container 내부의 group ID를 일치시키면 된다!!

 

 

[ About Gogs ]

Gogs : Gogs is a painless self-hosted Git service - Github , DockerHub , Docs

 

▶ Gogs 컨테이너 초기 설정 : Repo(Private)

더보기

1) 초기 설정 웹 접속

# 초기 설정 웹 접속
open "http://127.0.0.1:3000/install" # macOS
웹 브라우저에서 http://127.0.0.1:3000/install 접속 # Windows

 

2) 초기 설정 - 세부 항목 선택

  • 데이터베이스 유형 : SQLite3
  • 애플리케이션 URL : http://<각자 자신의 PC IP>:3000/
  • 기본 브랜치 : main
  • 관리자 계정 설정 클릭 : 이름(계정명 - 닉네임 사용 devops), 비밀번호(계정암호 qwe123), 이메일 입력

[ 실행 결과 - 한 눈에 보기 ]

☞ gogs 설치 부터 접속까지 ~ ^0^/

 

Gogs 설치하기 클릭 ⇒ 관리자 계정으로 로그인 후 접속

더보기
#
docker compose exec gogs ls -l /data
docker compose exec gogs ls -l /data/gogs
docker compose exec gogs ls -l /data/gogs/conf
docker compose exec gogs cat /data/gogs/conf/app.ini

  

  • 로그인 후 → Your Settings → Applications : Generate New Token 클릭 - Token Name(devops) ⇒ Generate Token 클릭 : 메모해두기!
69da4934069e3425a3ba2b029ddcccada1b37663

 

  • New Repository
    • 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 주소 확인
http://192.168.56.1:3000/devops/dev-app.git

Gogs 실습을 위한 저장소 설정 : jenkins 컨테이너 bash 내부 진입해서 git 작업 진행 ← 호스트에서 직접 git 작업하셔도 됩니다.

더보기
#
docker compose exec jenkins bash
-----------------------------------
whoami
pwd

cd /var/jenkins_home/
tree

#
git config --global user.name "<Gogs 계정명>"
git config --global user.name "devops"
git config --global user.email "a@a.com"
git config --global init.defaultBranch main

#
git clone <각자 Gogs dev-app repo 주소>
git clone http://192.168.254.124:3000/devops/dev-app.git
Cloning into 'dev-app'...
Username for 'http://192.168.254.124:3000': devops  # Gogs 계정명
Password for 'http://devops@192.168.254.124:3000': <토큰> # 혹은 계정암호
...

#
tree dev-app
cd dev-app
git branch
git remote -v

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

class RequestHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header('Content-type', 'text/plain')
        self.end_headers()
        now = datetime.now()
        response_string = now.strftime("The time is %-I:%M:%S %p, CloudNeta Study.\n")
        self.wfile.write(bytes(response_string, "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


# 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

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

[ 실행 결과 - 한 눈에 보기 ]

 

[ About Docker Hub ]

도커 허브 소개 - Docs

더보기

도커 허브(Docker Hub)는 도커 이미지 원격 저장소입니다.

사용자들은 도커 허브에 이미지를 업로드하고, 다른 곳에서 자유롭게 재사용(다운로드)할 수 있습니다.

여러 사용자가 자신이 만든 도커 이미지를 서로 자유롭게 공유할 수 있는 장을 마련해 줍니다.

  • 단, 도커 허브는 누구나 이미지를 올릴 수 있기 때문에 공식(Official) 라벨이 없는 이미지는 사용법을 찾을 수 없거나 제대로 동작하지 않을 수 있습니다.
  • [정보] 도커 악성 이미지를 통한 취약점 공격 - 기사 모음
    • 도커도 이제 공격 통로! 악성 이미지 늘어나고 있다 - 링크
    • 도커 환경 공격하는 해커들, 전략을 또 변경했다 - 링크
    • 암호화폐 채굴 공격자들, 잘못 설정된 도커 집중 공략 - 링크
    • 리눅스 노리던 봇넷 멀웨어 둘, 최근 들어 도커 서버도 노리기 시작 - 링크
    • 도커 호스트 감염시켜가며 암호화폐 채굴하는 웜 발견 - 링크
  • Docker Hub is also where you can go to carry out administrative tasks for organizations. If you have a Docker Team or Business subscription, you can also carry out administrative tasks in the Docker Admin Console.

Docker Hub quickstart - Docs

더보기
  • Step 1 : Sign up for a free Docker account
    • Docker ID grants you access to Docker Hub repositories and lets you explore available images from the community and verified publishers. You also need a Docker ID to share images on Docker Hub.
    • Explore Docker's core subscriptions to see what else Docker can offer you. → Personal(무료, 6시간 당 200개 Image pulls)
  • Step 2 : Create your first repository
    1. On the Repositories page, select Create repository.
    2. Name it <your-username>/dev-test-app
    3. Set the visibility to Private.
    4. Select Create.
  • Step 3 : Build and push a container image to Docker Hub from your computer
    1. Start by creating a Dockerfile to specify your application as shown below:
# syntax=docker/dockerfile:1
FROM busybox
CMD echo "Hello world! This is my first Docker image."

 

   2. Run docker build -t <your_username>/my-private-repo . to build your Docker image.

   3. Run docker run <your_username>/my-private-repo to test your Docker image locally.

   4. Run docker push <your_username>/my-private-repo to push your Docker image to Docker Hub. You should see output similar to :

# Dockerfile 파일 작성
cat > Dockerfile <<EOF
FROM busybox
CMD echo "Hello world! This is my first Docker image."
EOF
cat Dockerfile

# 빌드
DOCKERID=<자신의 도커 계정명>
DOCKERREPO=<자신의 도커 저장소>
DOCKERID=gasida
DOCKERREPO=dev-test-app

docker build -t $DOCKERID/$DOCKERREPO .
docker images | grep $DOCKERID/$DOCKERREPO

# 실행 확인
docker run $DOCKERID/$DOCKERREPO

# 로그인 확인
docker login

# 푸시
docker push $DOCKERID/$DOCKERREPO
  • push 후 확인
  • (참고) Name your local images using one of these methods:
    • When you build them, using docker build -t <hub-user>/<repo-name>[:<tag>]
    • By re-tagging the existing local image with docker tag <existing-image> <hub-user>/<repo-name>[:<tag>].
    • By using docker commit <existing-container> <hub-user>/<repo-name>[:<tag>] to commit changes.

☞ 자신의 도커 허브 계정에 dev-app (Private) repo 생성


3. Jenkins 기본 사용

3-1. Jenkins + Gogs (Private Repository) 연동하여 CICD 수행하기

▶ 작업 소개 (프로젝트, Job, Item) : 3가지 유형의 지시 사항 포함

더보기
  1. 작업을 수행하는 시점 Trigger
    • 작업 수행 태스크 task가 언제 시작될지를 지시
  2. 작업을 구성하는 단계별 태스크 Built step
    • 특정 목표를 수행하기 위한 태스크를 단계별 step로 구성할 수 있다.
    • 이것을 젠킨스에서는 빌드 스텝 build step이라고 부른다.
  3. 태스크가 완료 후 수행할 명령 Post-build action
    • 예를 들어 작업의 결과(성공 or 실패)를 사용자에게 알려주는 후속 동작이나, 자바 코드를 컴파일한 후 생성된 클래스 파일을 특정 위치로 복사 등
  • (참고) 젠킨스의 빌드 : 젠킨스 작업의 특정 실행 버전
    • 사용자는 젠킨스 작업을 여러번 실행할 수 있는데, 실행될 때마다 고유 빌드 번호가 부여된다.
    • 작업 실행 중에 생성된 아티팩트, 콘솔 로드 등 특정 실행 버전과 관련된 모든 세부 정보가 해당 빌드 번호로 저장된다.

작업 구성 Job Configuration : General 일반 섹션

더보기
  • 프로젝트 비활성화 : 오른쪽 위 토글 버튼 Enabled , Disabled
  • 오래된 빌드 삭제 Discards old builds : 삭제 시기 결정 옵션 - 기간(기본값 14일), 회숫(기본값 50개) → 제한값 초과 시 오래된 빌드 삭제

 

  • 빌드 이력 유지 기간(일) Days to keep builds : 14 입력 시 이 작업의 빌드를 최대 14일간 유지.
  • 빌드 유지 최대 개수(보관할 최대갯수) Max # of builds to keep : 50 입력 시 51번 이후에는 가장 오래된 빌드부터 순서대로 삭제.
  • 고급 - 아티팩트 유지 기간(산출물 보관 일수) Days to keep artifacts : 빌드 후 산출물(예. jar war) 유지 기간
  • 고급 - 아티팩트 유지 최대 개수 Max # of builds to keep with artifacts : 빌드 후 아티팩트 유지 개수

  • 매개변수형 프로젝트 (이 빌드는 매개변수가 있습니다 This project is parameterized) : 외부 입력 받을 때 사용

 

  • String Parameter 클릭 : 매개변수 명(FirstPara) , Default Value(xangle)
  • Build Steps : Execute shell 에 아래 입력  
echo "{$FirstPara}"

 

 

 

 

  • ‘파라미터와 함께 빌드’ 클릭 → 매개변수 확인 후 매개변수가 필요한 빌드입니다’ 클릭

 

  •  콘솔 output에서 확인
  • 동시 빌드 실행 (필요한 경우 concurrent 빌드 실행) Execute concurrent builds if necessary
    • 이 옵션은 독립적인 여러 단계로 분리될 수 있는 긴 빌드 프로세스 실행 시 유용
  • 고급 옵션 Advanced
    • 대기 시간 Quiet period : 옵션 사용 시 새로운 빌드가 즉시 시작되지 않는다. 빌드 큐(대기열)에 추가되고 지정된 시간이 지나야 빌드가 시작됨.
      • 아래 처럼 5초 입력 시, 빌드가 시작되기 전에 5초 동안 빌드 큐에서 대기함.
  • 재시도 횟수 Retry Count : 예를 들어 옵션 미 사용 시, SCM(깃)에 사용 작업(첫 번째 체크아웃) 시도가 실패하자마자 바로 작업 실패로 처리됨
    • 아래 처럼 옵션 사용(3) 입력 시, SCM 체크 아웃을 3번 시도하며, 재시도 사이의 시간 간격은 10초다

 

  • 업스트림(선행 작업) 프로젝트가 빌드 중일 때 빌드 차단 : 선행 프로젝트가 빌드 큐에 있을 때 이 프로젝트/작업을 실행하지 않음
  • 다운스트림(후행 작업) 프로젝트가 빌드 중일 때 빌드 차단 : 후행 프로젝트가 빌드 큐에 있을 때 이 프로젝트/작업을 실행하지 않음
  • 커스텀 워크스페이스 사용 (사용자 빌드 경로 사용 Use custom workspace) : 작업용 워크스페이스 기본 경로를 변경 가능
  • 의존성 빌드 로그 유지 Keep the build logs of dependencies : 현재 작업과 관련된 모든 빌드가 ‘로그 순환' 기능에서 제외. 즉 로그 유지! 

▶ Jenkins Item 생성 : item name(first) - docker 명령 실행 확인

더보기
  • Build Steps : Execute shell ⇒ Save 후 지금 빌드 : Console Output 확인
echo "docker check" | tee test.txt
docker ps
  • Item 이름별 작업 공간 workspace 확인
#
docker compose exec jenkins ls /var/jenkins_home/workspace
  • VSCODE(Docker 플러그인)에서 Jenkins 컨테이너의 workspace 확인

▶ Gogs Repo 자격증명 설정(gogs-dev-app) : Jenkins 관리 → Credentials → Globals → Add Credentials

더보기
  • Kind : Username with password
  • Username : devops
  • Password : <Gogs dev-app 토큰>
  • ID : gogs-dev-app

▶ Jenkins Item 생성 : item name(second) - 소스코드관리(Git) 설정 + 빌드 파라미터

더보기
  • 이 빌드는 매개변수가 있습니다 String Parameter 클릭 : 매개변수 명(FirstPara) , Default Value(CICD)
  • 소스코드관리 : Git 선택
    • Repository URL : http://***<mac IP>***:3000/***<Gogs 계정명>***/dev-app ← .git 은 제거
    • Credentials +Add 클릭(Jenkins :
    • Branches to build (Branch Specifier) : */main
  • Build Steps : Execute shell
echo "{$FirstPara}"
cat VERSION
  • Save 후 ‘파라미터와 함께 빌드’
  • Console Output 확인 : 과정 동작 확인
  • Item 이름별 작업 공간 workspace 확인
#
docker compose exec jenkins ls /var/jenkins_home/workspace
docker compose exec jenkins tree /var/jenkins_home/workspace/second
docker compose exec jenkins ls -l /var/jenkins_home/workspace/second

[ 실행 결과 - 한 눈에 보기 ]

- Jenkins Item 만들기 + gogs 연동 배포 테스트 (with. Parameter)

3-2. Pipeline 생성 실습

▶ Jenkins 설정 : Jenkins Plugin 설치 , Jenkins 도커 계정/암호 자격 증명 설정

더보기
  • Pipeline Stage View - Docs
  • Docker Pipeline : building, testing, and using Docker images from Jenkins Pipeline - Docs
  • Gogs : Webhook Plugin - Docs
    • 예시 : http(s)://<< jenkins-server >>/gogs-webhook/?job=<<jobname>>
  • Jenkins 도커 계정/암호 자격 증명 설정 : Add Credentials(Global) - Kind(Username with password)
    • Username : <도커 계정명>
    • Password : <도커 계정 암호 혹은 토큰>
    • ID : dockerhub-credentials ⇒ 자격증명 이름으로, pipeline 사용 예정

[ 실행 결과 - 한 눈에 보기 ]

 

파이프라인 Pipeline : CD 파이프라인을 구현하고 통합할 때 사용하는 플러그인 스크립트 모음 - Docs

더보기
[ 출처 : https://www.jenkins.io/doc/book/pipeline/ ]
  • 파이프라인 장점
    • 코드 : 애플리케이션 CI/CD 프로세스를 코드 형식으로 작성할 수 있고, 해당 코드를 중앙 리포지터리에 저장하여 팀원과 공유 및 작업 가능
    • 내구성 : 젠킨스 서비스가 의도적으로 또는 우발적으로 재시작되더라도 문제없이 유지됨
    • 일시 중지 가능 : 파이프라인을 실행하는 도중 사람의 승인이나 입력을 기다리기 위해 중단하거나 기다리는 것이 가능
    • 다양성 : 분기나 반복, 병렬 처리와 같은 다양한 CI/CD 요구 사항을 지원

  • 파이프라인 용어

☞ Multi 환경에서의 배포가 필요할 경우, stages{ } 안에 개별 stage(){ } 에 대하 설정으로 정의할 수 있다!!

  • 파이프라인 : 전체 빌드 프로세스를 정의하는 코드.
  • 노드 node = Agent : 파이프라인을 실행하는 시스템
  • Stages : 순차 작업 명세인 stage 들의 묶음
  • stage : 특정 단계에서 수행되는 작업들의 정의. (옵션) agents 설정
  • steps : 파이프라인의 특정 단계에서 수행되는 단일 작업을 의미.
  • post : 빌드 후 조치, 일반적으로 stages 작업이 끝난 후 추가적인 steps/step
  • Directive : environment, parameters, triggers, input, when - Docs
    • environment (key=value) : 파이프라인 내부에서 사용할 환경변수
    • parameters : 입력 받아야할 변수를 정의 - Type(string, text, choice, password …)
    • when : stage 를 실행 할 조건 설정

  • 파이프라인 3가지 구성 형태
    • Pipeline script : 일반적인 방식으로 Jenkins 파이프라인을 생성하여 Shell Script를 직접 생성하여 빌드하는 방식
    • Pipeline script from SCM : 사전 작성한 JenkinsFile을 형상관리 저장소에 보관하고, 빌드 시작 시 파이프라인 프로젝트에서 호출 실행하는 방식
      • In SCM - you can write a Jenkinsfile manually, which you can commit to your project’s source control repository.
  • Blue Ocean 기반 : UI기반하여 시각적으로 파이프라인을 구성하면, JenkinsFile이 자동으로 생성되어 실행되는 방식
    • Through Blue Ocean - after setting up a Pipeline project in Blue Ocean, the Blue Ocean UI helps you write your Pipeline’s Jenkinsfile and commit it to source control.
[ 출처 : https://www.jenkins.io/doc/book/blueocean/pipeline-run-details/ ]

  • 파이프라인 2가지 구문 : 선언형 파이프라인(권장)과 스크립트형 파이프라인
    • 선언형 파이프라인 : 쉽게 작성 가능, 최근 문법이고 젠킨스에서 권장하는 방법, step 필수!
    • 스크립트형 파이프라인 : 커스텀 작업에 용이, 복잡하여 난이도가 높음, step은 필수 아님

[ 스트립트형 예제 ]

node {          # 	Execute this Pipeline or any of its stages, on any available agent.
    stage('Build') {    # Defines the "Build" stage. stage blocks are optional in Scripted Pipeline syntax. However, implementing stage blocks in a Scripted Pipeline provides clearer visualization of each stage's subset of tasks/steps in the Jenkins UI.
        //              # Perform some steps related to the "Build" stage.
    }
    stage('Test') { 
        // 
    }
    stage('Deploy') { 
        // 
    }
}

[ 선언형 예제 ]

pipeline {
    agent any     # Execute this Pipeline or any of its stages, on any available agent.
    stages {
        stage('Build') {   # Defines the "Build" stage.
            steps {
                //         # Perform some steps related to the "Build" stage.
            }
        }
        stage('Test') { 
            steps {
                // 
            }
        }
        stage('Deploy') { 
            steps {
                // 
            }
        }
    }
}

 

 

▶ Jenkins Pipeline : Item : First-Pipeline

더보기

1. Item : First-Pipeline → Pipeline(Pipeline script - 아래 내용 복붙)

 1) 먼저 샘플 파이프라인 테스트 해보자!

 

 2) Hello World 테스트 → [지금 빌드]

pipeline {
    agent any

    stages {
        stage('Hello') {
            steps {
                echo 'Hello World'
            }
        }
        stage('Deploy') {
            steps {
                echo "Deployed successfully!";
            }
        }
    }
}

 

 

3) 아래 처럼 수정 후 확인: 환경변수 사용, 문자열 보간 → Console Output 확인

pipeline {
    agent any
    environment { 
        CC = 'clang'
    }
    
    stages {
        stage('Example') {
            environment { 
                AN_ACCESS_KEY = 'abcdefg'
            }
            steps {
                echo "${CC}";
                sh 'echo ${AN_ACCESS_KEY}'
            }
        }
    }
}

 

 4) 아래 처럼 수정 후 확인: 파이프라인 빌드 시작(트리거) → Console Output 확인

pipeline {
    agent any
    triggers {
        cron('H */4 * * 1-5')
    }
    stages {
        stage('Example') {
            steps {
                echo 'Hello World'
            }
        }
    }
}

 

 5) 아래 처럼 수정 후 확인: 파라미터와 함께 빌드 → Console Output 확인 ⇒ 다시 한번 더 빌드 클릭 (변수 입력 칸 확인)

pipeline {
    agent any
    parameters {
        string(name: 'PERSON', defaultValue: 'Mr Jenkins', description: 'Who should I say hello to?')
        text(name: 'BIOGRAPHY', defaultValue: '', description: 'Enter some information about the person')
        booleanParam(name: 'TOGGLE', defaultValue: true, description: 'Toggle this value')
        choice(name: 'CHOICE', choices: ['One', 'Two', 'Three'], description: 'Pick something')
        password(name: 'PASSWORD', defaultValue: 'SECRET', description: 'Enter a password')
    }
    stages {
        stage('Example') {
            steps {
                echo "Hello ${params.PERSON}"
                echo "Biography: ${params.BIOGRAPHY}"
                echo "Toggle: ${params.TOGGLE}"
                echo "Choice: ${params.CHOICE}"
                echo "Password: ${params.PASSWORD}"
            }
        }
    }
}

 

 6)  아래 처럼 수정 후 확인: post (빌드 후 조치) → Console Output 확인

  • always: 항상 실행
  • changed: 현재 빌드의 상태가 이번 빌드의 상태와 달라졌다면 실행
  • success: 현재 빌드가 성공했다면 실행
  • failure: 현재 빌드가 실패했다면 실행
  • unstable: 현재 빌드의 상태가 불안하다면 실행
pipeline {
    agent any
    stages {
        stage('Example') {
            steps {
                echo 'Hello World'
            }
        }
    }
    post { 
        always { 
            echo 'I will always say Hello again!'
        }
    }
}

 

7) 추가 수정후 확인

pipeline {
    agent any
    stages {
        stage('Compile') {
            steps {
                echo "Compiled successfully!";
            }
        }

        stage('JUnit') {
            steps {
                echo "JUnit passed successfully!";
            }
        }

        stage('Code Analysis') {
            steps {
                echo "Code Analysis completed successfully!";
            }
        }

        stage('Deploy') {
            steps {
                echo "Deployed successfully!";
            }
        }
    }
}

 

 8) 지금 빌드 : 빌드 단계 걸린 시간, 빌드 단계 별 로그

  ☞ Console Output 확인


 2. Item : My-First-Pipeline , Pipeline(Pipeline script - 아래 내용 추가 복붙) post 항목 : 빌드 성공 실패 등 메시지 출력

pipeline {
    agent any
    stages {
        stage('Compile') {
            steps {
                echo "Compiled successfully!";
            }
        }

        stage('JUnit') {
            steps {
                echo "JUnit passed successfully!";
            }
        }

        stage('Code Analysis') {
            steps {
                echo "Code Analysis completed successfully!";
            }
        }

        stage('Deploy') {
            steps {
                echo "Deployed successfully!";
            }
        }
    }

    post {
      always {
        echo "This will always run"
      }
      success {
        echo "This will run when the run finished successfully"
      }
      failure {
        echo "This will run if failed"
      }
      unstable {
        echo "This will run when the run was marked as unstable"
      }
      changed {
        echo "This will run when the state of the pipeline has changed"
      }
    }
}

 

1) 지금 빌드! : 빌드 실행 결과 정보 확인 - Declarative : Post Actions 항목 추가 확인 → 선택 후 logs 클릭 확인 ⇒ 상단에 결과 정보 확인

 

2) Pipeline Syntax → Snippet Generator 스내펫 생성시 사용하기 ⇒ sample(sh), shell Script

▶ Jenkins Item 생성(Pipeline) : item name(pipeline-ci)

1) Pipeline 스크립트 입력

더보기
pipeline {
    agent any
    environment {
        DOCKER_IMAGE = '<자신의 도커 허브 계정>/dev-app' // Docker 이미지 이름
    }
    stages {
        stage('Checkout') {
            steps {
                 git branch: 'main',
                 url: 'http://192.168.254.124:3000/devops/dev-app.git',  // Git에서 코드 체크아웃
                 credentialsId: 'gogs-dev-app'  // 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-credentials') {
                        // DOCKER_TAG 사용
                        def appImage = docker.build("${DOCKER_IMAGE}:${DOCKER_TAG}")
                        appImage.push()
                    }
                }
            }
        }
    }
    post {
        success {
            echo "Docker image ${DOCKER_IMAGE}:${DOCKER_TAG} has been built and pushed successfully!"
        }
        failure {
            echo "Pipeline failed. Please check the logs."
        }
    }
}

 



2) 지금 빌드 → 콘솔 Output 확인

3) 도커허브 확인

 4) Jenkins 컨테이너가 이미지 빌드하여, 호스트에서도 컨테이너 이미지 생성 확인

 [ 실행 결과 - 한 눈에 보기 ]  

※ git branch - URL 수정 시, 뒤에 '.git' 을 빼주어야 에러가 나지 않는다!!

 


4. Docker 기반 어플리케이션 CI/CD 구성

☞ 개발자가 source 를 개발하여 gogs(Private 저장소) 에 업로드 하면, Jenkins 에서 자동으로 catch 하여 Deploy 까지 연계하는 과정에 대한 실습을 해 보자!!

 

▶ Gogs Webhooks 설정 : Jenkins Job Trigger

더보기

[ Trouble-shooting ]

- gogs 에 app.ini 파일 수정 후 컨테이너 재기동 - issue

[security]
INSTALL_LOCK = true
SECRET_KEY   = j2xaUPQcbAEwpIu
LOCAL_NETWORK_ALLOWLIST = 192.168.254.124 # 각자 자신의 PC IP

 

☞ docker compose restart gogs

 

  • Webhooks are much like basic HTTP POST event triggers. Whenever something occurs in Gogs,
  • we will handle the notification to the target host you specify. %!(EXTRA string=https://gogs.io/docs/features/webhook.html)
  • Payload URL : http://<자신의 PC 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

 

(옵션) Jenkins 에서 Item 생성 후 Test Delivery 후 확인


 [ 실행 결과 - 한 눈에 보기 ]

 

▶ Jenkins Item 생성(Pipeline) : item name(SCM-Pipeline)

더보기
  • GitHub project : http://***<mac IP>***:3000/***<Gogs 계정명>***/dev-app ← .git 은 제거
  • Use Gogs secret : qwe123
  • Build Triggers : Build when a change is pushed to Gogs 체크
  • Pipeline script from SCM
    • SCM : Git
      • Repo URL(http://***<mac IP>***:3000/***<Gogs 계정명>***/dev-app)
      • Credentials(devops/***)
      • Branch(*/main)
    • Script Path : Jenkinsfile

 

 

Jenkinsfile 작성 후 Git push  

더보기

Step1. jenkins 컨테이너(혹은 로컬에서 git 작업)에서 아래 작업

# Jenkinsfile 빈 파일 작성
docker compose exec jenkins touch /var/jenkins_home/dev-app/Jenkinsfile

# 버전 0.0.2 수정 : 아래 입력 잘 안될 경우 VSCODE(Docker플러그인)에서 직접 수정
docker compose exec jenkins sh -c 'echo "0.0.2" > /var/jenkins_home/dev-app/VERSION'

 

Step2.  VSCODE 로 jenkins 컨테이너 내부 파일 Open 후 작성

pipeline {
    agent any
    environment {
        DOCKER_IMAGE = '<자신의 도커 허브 계정>/dev-app' // Docker 이미지 이름
    }
    stages {
        stage('Checkout') {
            steps {
                 git branch: 'main',
                 url: 'http://192.168.254.124:3000/devops/dev-app.git',  // Git에서 코드 체크아웃
                 credentialsId: 'gogs-dev-app'  // 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-credentials') {
                        // DOCKER_TAG 사용
                        def appImage = docker.build("${DOCKER_IMAGE}:${DOCKER_TAG}")
                        appImage.push()
                    }
                }
            }
        }
    }
    post {
        success {
            echo "Docker image ${DOCKER_IMAGE}:${DOCKER_TAG} has been built and pushed successfully!"
        }
        failure {
            echo "Pipeline failed. Please check the logs."
        }
    }
}

 

Step3. 작성된 파일 push : jenkins 컨테이너 bash 내부 진입해서 작업 진행  

 

Step4. Jenkins 트리거 빌드 확인

 

Step5. Docker hub 확인

 

Step6. Gogs WebHook 기록 확인

 

Step7. server.py 와 VERSION 변경 후 Jenkins 트리거 작업 한번 더 확인하기

# server.py

# 버전 0.0.3 수정 : 아래 입력 잘 안될 경우 VSCODE(Docker플러그인)에서 직접 수정
docker compose exec jenkins sh -c 'echo "0.0.3" > /var/jenkins_home/dev-app/VERSION'

#
docker compose exec jenkins bash
---------------------------------
cd /var/jenkins_home/dev-app/
git add . && git commit -m "VERSION $(cat VERSION) Changed" && git push -u origin main

 


 [ 실행 결과 - 한 눈에 보기 ]

 

Final : 도커 빌드 후 기존 컨테이너 중지/제거 후 신규 컨테이너 실행 Jenkinsfile pipeline 수정 후 빌드 (SCM-Pipeline)

더보기

Step1. Jenkinsfile 수정 후 git push → tcp (기본값:4000) 는 파라미터로 입력 받게 설정하게 해볼것

pipeline {
    agent any
    environment {
        DOCKER_IMAGE = '<자신의 도커 허브 계정>/dev-app' // Docker 이미지 이름
        CONTAINER_NAME = 'dev-app' // 컨테이너 이름
    }
    stages {
        stage('Checkout') {
            steps {
                 git branch: 'main',
                 url: 'http://192.168.254.124:3000/devops/dev-app.git',  // Git에서 코드 체크아웃
                 credentialsId: 'gogs-dev-app'  // 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-credentials') {
                        // DOCKER_TAG 사용
                        def appImage = docker.build("${DOCKER_IMAGE}:${DOCKER_TAG}")
                        appImage.push()
                        appImage.push("latest")  // 빌드 이미지 push 할 때, 2개의 버전(현재 버전, latest 버전)을 업로드
                    }
                }
            }
        }
        stage('Check, Stop and Run Docker Container') {
            steps {
                script {
                    // 실행 중인 컨테이너 확인
                    def isRunning = sh(
                        script: "docker ps -q -f name=${CONTAINER_NAME}",
                        returnStdout: true
                    ).trim()
                    
                    if (isRunning) {
                        echo "Container '${CONTAINER_NAME}' is already running. Stopping it..."
                        // 실행 중인 컨테이너 중지
                        sh "docker stop ${CONTAINER_NAME}"
                        // 컨테이너 제거
                        sh "docker rm ${CONTAINER_NAME}"
                        echo "Container '${CONTAINER_NAME}' stopped and removed."
                    } else {
                        echo "Container '${CONTAINER_NAME}' is not running."
                    }
                    
                    // 5초 대기
                    echo "Waiting for 5 seconds before starting the new container..."
                    sleep(5)
                    
                    // 신규 컨테이너 실행
                    echo "Starting a new container '${CONTAINER_NAME}'..."
                    sh """
                    docker run -d --name ${CONTAINER_NAME} -p 4000:80 ${DOCKER_IMAGE}:${DOCKER_TAG}
                    """
                }
            }
        }
    }
    post {
        success {
            echo "Docker image ${DOCKER_IMAGE}:${DOCKER_TAG} has been built and pushed successfully!"
        }
        failure {
            echo "Pipeline failed. Please check the logs."
        }
    }
}

 

 Step2. 생성된 컨테이너 접속 확인

docker image
docker ps
curl http://127.0.0.1:4000

  

Step3. server.py 수정 후 VERSION 수정 후 push 후 생성된 컨테이너 접속 후 반영 확인

# server.py 수정
response_string = now.strftime("The time is %-I:%M:%S %p, Study 1week End.\n")

# VERSION 수정

# Jenkins 컨테이너 내부에서 git push
jenkins@5c1ba7016f9e:~/dev-app$ git add . && git commit -m "VERSION $(cat VERSION) Changed" && git push -u origin main

# 호스트 PC에서 반복 접속 실행 : 서비스 중단 시간 체크!
while true; do curl -s --connect-timeout 1 http://127.0.0.1:4000 ; date; sleep 1 ; done

 


 [ 실행 결과 - 한 눈에 보기 ]

 


[ 마무리 ]

이번 과정을 통해서  Jenkins 와 gogs를 활용한 CICD 배포 관리의 pipeline 동작 방식에 대해 배워 볼 수 있었다.

마지막 실습을 통해 Seamless Deploy 관련 생각해 보게 되었고, 현업에서 주로 사용되는 EKS 배포 중 B/G 업데이트 시 종종 발생하는 유의해야 할 점들에 좀 더 면밀히 보아야 겠다는 생각이 들었다.


[ 참고 링크 모음 ]

더보기

1. 도커 compose : 공식링크Blog

 - 설치 : https://docs.docker.com/compose/install/

 - 옵션 : https://docs.docker.com/reference/cli/docker/compose

 

2. (참고) VSCODE 플러그인 : jenkins-pipeline-linter-connector - Link

 

'CICD' 카테고리의 다른 글

3주차 - Jenkins CI/ArgoCD + K8S  (0) 2024.12.16
2주차 - GitHub Actions CI/CD  (1) 2024.12.09