티스토리 뷰
CloudNet@ 가시다님이 진행하는 CI/CD 맛보기 스터디 내용 참고.
0. 실습 환경 구성
windows PC에서 1주차 실습을 하기 위해 환경을 구축 합니다.
1. WSL2 + Docker Desktop , VSCODE
- 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 재시작 #
####################
# 설치 가능한 배포판 확인
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 설치
- WSL 2부터는 Docker를 WSL 2 리눅스 안에 직접 설치할 수도 있지만, 이 방법보다는 Docker Desktop for Windows를 이용하는 것을 추천
- https://wslhub.com/wsl-firststep/firststep/docker/
- https://www.lainyzine.com/ko/article/a-complete-guide-to-how-to-install-docker-desktop-on-windows-10/
- 설치 후 Docker Settings 체크 → 도커 엔진 재시작
- 설치 후 확인 : 관리자 권한으로 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)
- VSCODE 개발 편리 설정 : auto-save 등등
- VSCODE 확장 프로그램 설치
1. 컨테이너를 활용한 애플리케이션 개발
- 실습 시나리오 참고 : https://github.com/WilliamDenniss/kubernetes-for-developers Chapter02
1.1 python 으로 특정 문자열 출력
- WSL Ubuntu 내부에서 아래 작업 진행
#
mkdir 1.1
cd 1.1
code .
# 코드 작성
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
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.4 Jib로 자바 컨테이너 빌드 - Docs , Blog1 , Blog2
- Jib는 Dockerfile을 사용하지 않거나 Docker를 설치할 필요 없이 컨테이너를 빌드합니다. → 예를 들어 젠킨스 컨테이너 내부에 도커 설치 필요 X
- Maven 또는 Gradle용 Jib 플러그인에서 Jib를 사용하거나 Jib 자바 라이브러리를 사용할 수 있습니다.
- Jib는 애플리케이션을 컨테이너 이미지로 패키징하는 모든 단계를 처리합니다.
- Dockerfile을 만들거나 Docker를 설치하기 위한 권장사항을 알 필요가 없습니다.
- Docker 빌드 흐름 : 도커 파일 작성 → 이미지 빌드 → 저장소에 이미지 푸시
- Jib 빌드 흐름 : 프로젝트에서 빌드와 동시에 이미지 만들어지고 저장소에 푸시까지 처리.
- Jib는 애플리케이션을 종속 항목, 리소스, 클래스 등 별개의 레이어로 구성하고 Docker 이미지 레이어 캐싱을 활용하여 변경사항만 다시 빌드함으로써 빌드를 빠르게 유지합니다.
- Jib 레이어 구성과 작은 기본 이미지는 전체 이미지 크기를 작게 유지하여 성능과 휴대성을 향상시킵니다.
1.5 Containerizing an application server
- HTTP 서버를 컨테이너화하는 방법을 설명하려면, HTTP 서버가 필요합니다!
- 현재 날짜와 시간을 반환하는 간단한 Python HTTP 서버 예제를 아래에 제공합니다.
# 코드 작성
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
- 변경된 코드 정보 반영 방안은?
#
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
#
# 컨테이너 이미지 빌드 : 용량 비교 해보자!
docker build . -t timeserver:2 && docker tag timeserver:2 timeserver:latest
docker image ls -f reference=timeserver
# 컨테이너 실행
docker run -d -p 8080:80 --name=timeserver timeserver
# 컨테이너 접속 및 로그 확인
curl http://localhost:8080
# 컨테이너 삭제
docker rm -f timeserver
1.6 Using Docker Compose for local testing : 개발 편리 방안 - Mapping folders locally, 코드 내용 동적 반영
- 호스트 현재 디렉터리를 컨테이너의 /app 경로로 볼륨 마운트 → 호스트 파일 업데이트 시 변경 사항 재구성 없이 컨테이너 내부에서 즉시 읽을 수 있음.
- Disk 에 Python 코드가 변경이 되더라도 reload 되지 않는다. 하여 코드를 업데이트하여 코드 변경 시 reload 되게 설정합니다.
- 파이썬의 경우 reloading 라이브러리를 사용하여 GET 기능으로 Disk 부터 reload 하게 됩니다 → 언어/프레임웤 별로 다름
#
# 코드 작성
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
2. CI/CD 실습 환경 구성
☞ 구성 : 컨테이터 2대(Jenkins, gogs) : 호스트 OS 포트 노출(expose)로 접속 및 사용
- 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
- Jenkins 컨테이너 초기 설정
# Jenkins 초기 암호 확인
docker compose exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword
ef79b4c0f1d84b41bb150045646cef54
# Jenkins 웹 접속 주소 확인 : 계정 / 암호 입력 >> admin / qwe123
open "http://127.0.0.1:8080" # macOS
웹 브라우저에서 http://127.0.0.1:8080 접속 # Windows
# (참고) 로그 확인 : 플러그인 설치 과정 확인
docker compose logs jenkins -f
- Jenkins URL 설정 : 각자 자신의 PC의 IP를 입력
☞ (참고) 실습 완료 후 해당 컨테이너 중지 상태로 둘 경우 → 재부팅 및 이후에 다시 실습을 위해 컨테이너 시작 시
# 실습 완료 후 해당 컨테이너 중지 상태로 둘 경우
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
Jenkins
☞ 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
- 최신 코드 가져오기 : 개발을 위해 중앙 코드 리포지터리에서 로컬 시스템으로 애플리케이션의 최신 코드를 가져옴
- 단위 테스트 구현과 실행 : 코드 작성 전 단위 테스트 케이스를 먼저 작성
- 코드 개발 : 실패한 테스트 케이스를 성공으로 바꾸면서 코드 개발
- 단위 테스트 케이스 재실행 : 단위 테스트 케이스 실행 시 통과(성공!)
- 코드 푸시와 병합 : 개발 소스 코드를 중앙 리포지터리로 푸시하고, 코드 병합
- 코드 병합 후 컴파일 : 변경 함수 코드가 병함되면 전체 애플리케이션이 컴파일된다
- 병합된 코드에서 테스트 실행 : 개별 테스트뿐만 아니라 전체 통합 테스트를 실행하여 문제 없는지 확인
- 아티팩트 배포 : 애플리케이션을 빌드하고, 애플리케이션 서버의 프로덕션 환경에 배포
- 배포 애플리케이션의 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 vs 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
Gogs
Gogs : Gogs is a painless self-hosted Git service - Github , DockerHub , Docs
☞ Gogs 컨테이너 초기 설정 : Repo(Private)
- 초기 설정 웹 접속
# 초기 설정 웹 접속
# open "http://127.0.0.1:3000/install" # macOS
웹 브라우저에서 http://127.0.0.1:3000/install 접속 # Windows
- 초기 설정
- 데이터베이스 유형 : SQLite3
- 애플리케이션 URL : http://<각자 자신의 PC IP>:3000/
- 기본 브랜치 : main
- 관리자 계정 설정 클릭 : 이름(계정명 - 닉네임 사용 devops), 비밀번호(계정암호 qwe123), 이메일 입력
→ 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 클릭 : 메모해두기!
3ff36efefb736150e42b800c81ad7d1727d4a8c6
- 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
☞ 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
...
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.
- Key features
- Repositories: Push and pull container images.
- Builds: Automatically build container images from GitHub and Bitbucket and push them to Docker Hub.
- Webhooks: Trigger actions after a successful push to a repository to integrate Docker Hub with other services.
- Docker Hub CLI tool (currently experimental) and an API that allows you to interact with Docker Hub.
- Browse through the Docker Hub API documentation to explore the supported endpoints.
- Administrative task
- Create and manage teams and organizations
- Create a company
- Enforce sign in
- Set up SSO and SCIM
- Use Group mapping
- Carry out domain audits
- Use Image Access Management to control developers' access to certain types of images
- Turn on Registry Access Management
- Docker Hub Webhooks - Docs
- You can use webhooks to cause an action in another service in response to a push event in the repository. Webhooks are POST requests sent to a URL you define in Docker Hub.
- Set up Automated Builds : 저장소를 구성하여 소스 공급자에게 새 코드를 푸시할 때마다 자동으로 이미지를 빌드 - Docs
- Automated builds require a Docker Pro, Team, or Business subscription.
3. Jenkins 기본 사용
☞ 작업 소개 (프로젝트, Job, Item) : 3가지 유형의 지시 사항 포함
- 작업을 수행하는 시점 Trigger
- 작업 수행 태스크 task가 언제 시작될지를 지시
- 작업을 구성하는 단계별 태스크 Built step
- 특정 목표를 수행하기 위한 태스크를 단계별 step로 구성할 수 있다.
- 이것을 젠킨스에서는 빌드 스텝 build step이라고 부른다.
- 태스크가 완료 후 수행할 명령 Post-build action
- 예를 들어 작업의 결과(성공 or 실패)를 사용자에게 알려주는 후속 동작이나, 자바 코드를 컴파일한 후 생성된 클래스 파일을 특정 위치로 복사 등
- (참고) 젠킨스의 빌드 : 젠킨스 작업의 특정 실행 버전
- 사용자는 젠킨스 작업을 여러번 실행할 수 있는데, 실행될 때마다 고유 빌드 번호가 부여된다.
- 작업 실행 중에 생성된 아티팩트, 콘솔 로드 등 특정 실행 버전과 관련된 모든 세부 정보가 해당 빌드 번호로 저장된다.
☞ 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://192.168.68.12:3000/devops/dev-app ← .git 은 제거
- Credentials +Add 클릭
- 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 설정 : Jenkins Plugin 설치 , Jenkins 도커 계정/암호 자격 증명 설정
- Jenkins Plugin 설치
- Jenkins 도커 계정/암호 자격 증명 설정 : Add Credentials(Global) - Kind(Username with password)
- Username : <도커 계정명>
- Password : <도커 계정 암호
혹은 토큰> - ID : dockerhub-credentials ⇒ 자격증명 이름으로, pipeline 사용 예정
☞ 파이프라인 Pipeline : CD 파이프라인을 구현하고 통합할 때 사용하는 플러그인 스크립트 모음 - Docs
- 파이프라인 장점
- 코드 : 애플리케이션 CI/CD 프로세스를 코드 형식으로 작성할 수 있고, 해당 코드를 중앙 리포지터리에 저장하여 팀원과 공유 및 작업 가능
- 내구성 : 젠킨스 서비스가 의도적으로 또는 우발적으로 재시작되더라도 문제없이 유지됨
- 일시 중지 가능 : 파이프라인을 실행하는 도중 사람의 승인이나 입력을 기다리기 위해 중단하거나 기다리는 것이 가능
- 다양성 : 분기나 반복, 병렬 처리와 같은 다양한 CI/CD 요구 사항을 지원
- 파이프라인 용어
- 파이프라인 : 전체 빌드 프로세스를 정의하는 코드.
- 노드 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를 직접 생성하여 빌드하는 방식
- Through the classic UI - you can enter a basic Pipeline directly in Jenkins through the classic UI.
- 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.
- Pipeline script : 일반적인 방식으로 Jenkins 파이프라인을 생성하여 Shell Script를 직접 생성하여 빌드하는 방식
- 파이프라인 2가지 구문 : 선언형 파이프라인(권장)과 스크립트형 파이프라인
- 선언형 파이프라인 : 쉽게 작성 가능, 최근 문법이고 젠킨스에서 권장하는 방법, step 필수!
- 스크립트형 파이프라인 : 커스텀 작업에 용이, 복잡하여 난이도가 높음, step은 필수 아님
- 선언형 Declarative 파이프라인
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 {
//
}
}
}
}
- 스크립트형 Scripted 파이프라인
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') {
//
}
}
☞ Jenkins Pipeline : Item : First-Pipeline
- Item : First-Pipeline → Pipeline(Pipeline script - 아래 내용 복붙)
- 먼저 샘플 파이프라인 테스트
- Hello World 테스트 → 지금 빌드
pipeline {
agent any
stages {
stage('Hello') {
steps {
echo 'Hello World'
}
}
stage('Deploy') {
steps {
echo "Deployed successfully!";
}
}
}
}
- 아래 처럼 수정 후 확인: 환경변수 사용, 문자열 보간 → Console Output 확인
pipeline {
agent any
environment {
CC = 'clang'
}
stages {
stage('Example') {
environment {
AN_ACCESS_KEY = 'abcdefg'
}
steps {
echo "${CC}";
sh 'echo ${AN_ACCESS_KEY}'
}
}
}
}
- 아래 처럼 수정 후 확인: 파이프라인 빌드 시작(트리거) → Console Output 확인
pipeline {
agent any
triggers {
cron('H */4 * * 1-5')
}
stages {
stage('Example') {
steps {
echo 'Hello World'
}
}
}
}
- 아래 처럼 수정 후 확인: 파라미터와 함께 빌드 → 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}"
}
}
}
}
- 아래 처럼 수정 후 확인: 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!'
}
}
}
- 아래 처럼 수정 후 확인
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!";
}
}
}
}
- 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"
}
}
}
- Pipeline Syntax → Snippet Generator 스내펫 생성시 사용하기 ⇒ sample(sh), shell Script
☞ Jenkins Item 생성(Pipeline) : item name(pipeline-ci)
- Pipeline script
pipeline {
agent any
environment {
DOCKER_IMAGE = '****578/dev-app' // Docker 이미지 이름
}
stages {
stage('Checkout') {
steps {
git branch: 'main',
url: 'http://192.168.68.12: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."
}
}
}
- 지금 빌드 → 콘솔 Output 확인
- 도커 허브 확인
- Jenkins 컨테이너가 이미지 빌드하여, 호스트에서도 컨테이너 이미지 생성 확인
4. 도커 기반 애플리케이션 CI/CD 구성
목표 구성 : Gogs → Jenkins → DockerHub / DockerEngine(Run)
☞ Gogs Webhooks 설정 : Jenkins Job Trigger
- 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)
☞ Jenkins Item 생성(Pipeline) : item name(SCM-Pipeline)
- GitHub project : http://192.168.68.12:3000/devops/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://192.168.68.12:3000/devops/dev-app)
- Credentials(devops/***)
- Branch(*/main)
- Script Path : Jenkinsfile
- SCM : Git
☞ Jenkinsfile 작성 후 Git push
- 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'
- VSCODE 로 jenkins 컨테이너 내부 파일 Open 후 작성
pipeline {
agent any
environment {
DOCKER_IMAGE = '****578/dev-app' // Docker 이미지 이름
}
stages {
stage('Checkout') {
steps {
git branch: 'main',
url: 'http://192.168.68.12: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."
}
}
}
- 작성된 파일 push : jenkins 컨테이너 bash 내부 진입해서 작업 진행 ← 호스트에서 직접 작업하셔도 됩니다.
#
docker compose exec jenkins bash
---------------------------------
cd /var/jenkins_home/dev-app/
git add . && git commit -m "Jenkinsfile add & VERSION 0.0.2 Changed" && git push -u origin main
☞ Final : 도커 빌드 후 기존 컨테이너 중지/제거 후 신규 컨테이너 실행 Jenkinsfile pipeline 수정 후 빌드 (SCM-Pipeline)
- Jenkinsfile 수정 후 git push → tcp (기본값:4000) 는 파라미터로 입력
pipeline {
agent any
environment {
DOCKER_IMAGE = '****578/dev-app' // Docker 이미지 이름
CONTAINER_NAME = 'dev-app' // 컨테이너 이름
}
stages {
stage('Checkout') {
steps {
git branch: 'main',
url: 'http://192.168.68.12: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."
}
}
}
- 생성된 컨테이너 접속 확인
docker image
docker ps
curl http://127.0.0.1:4000
- 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
'CICD 맛보기 Study' 카테고리의 다른 글
[3주차] - Jenkins CI/ArgoCD + K8S - #2 (0) | 2024.12.21 |
---|---|
[3주차] - Jenkins CI/ArgoCD + K8S - #1 (0) | 2024.12.21 |
[2주차] - GitHub Actions CI/CD (0) | 2024.12.14 |