올 해 마무리로 '가시다'의 스터디를 참여하게 되었습니다.
바로 cicd스터디 1주차 공유 시작하겠습니다.
1. 컨테이너를 활용한 애플리케이션 개발
1.1 python 으로 특정 문자열 출력 → Q. 컨테이너 이미지 Tag 태그 사용 방안
# 코드 작성
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 빌드 흐름 : 프로젝트에서 빌드와 동시에 이미지 만들어지고 저장소에 푸시까지 처리. 개발자가 편하다!
1.5 Containerizing an application server
# 코드 작성
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
컨테이너에 접속 후 수정했으나 반영이 안된다 (사진 우측 상단 CloudNet@로 변경)
변경된 코드 정보 반영 방안은?
#
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, 코드 내용 동적 반영
파이썬의 경우 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)로 접속 및 사용
# 작업 디렉토리 생성 후 이동
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
09a21116f3ce4f27a0ede79372febfb1
# Jenkins 웹 접속 주소 확인 : 계정 / 암호 입력 >> admin / qwe123
open "http://127.0.0.1:8080" # macOS
웹 브라우저에서 http://127.0.0.1:8080 접속 # Windows
# (참고) 로그 확인 : 플러그인 설치 과정 확인
docker compose logs jenkins -f
젠킨스의 간단한 소개
- Jenkins : The open source automation server, support building deploying and automating any project - DockerHub , Github , Docs
- 최신 코드 가져오기 : 개발을 위해 중앙 코드 리포지터리에서 로컬 시스템으로 애플리케이션의 최신 코드를 가져옴
- 단위 테스트 구현과 실행 : 코드 작성 전 단위 테스트 케이스를 먼저 작성
- 코드 개발 : 실패한 테스트 케이스를 성공으로 바꾸면서 코드 개발
- 단위 테스트 케이스 재실행 : 단위 테스트 케이스 실행 시 통과(성공!)
- 코드 푸시와 병합 : 개발 소스 코드를 중앙 리포지터리로 푸시하고, 코드 병합
- 코드 병합 후 컴파일 : 변경 함수 코드가 병함되면 전체 애플리케이션이 컴파일된다
- 병합된 코드에서 테스트 실행 : 개별 테스트뿐만 아니라 전체 통합 테스트를 실행하여 문제 없는지 확인
- 아티팩트 배포 : 애플리케이션을 빌드하고, 애플리케이션 서버의 프로덕션 환경에 배포
- 배포 애플리케이션의 E-E 테스트 실행 : 셀레늄 Selenium과 같은 User Interface 자동화 도구를 통해 애플리케이션의 전체 워크플로가 정상 동작하는지 확인하는 종단간 End-to-End 테스트를 실행.
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 -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
설치 완료
→ 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 클릭 : 메모해두기!
New Repository
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
...
push완료
'study > CICD' 카테고리의 다른 글
CICD 스터디 3주차 두번째 (0) | 2024.12.22 |
---|---|
CICD 스터디 3주차 첫번째 (0) | 2024.12.22 |
CICD 스터디 2주차 (1) | 2024.12.15 |
CICD 스터디 1주차 두번째 (0) | 2024.12.07 |