Docker CI ์์๋ณด๊ธฐ
KANS ์คํฐ๋ 1์ฃผ์ฐจ Docker CI ์์๋ณด๊ธฐ
๐ก KANS ์คํฐ๋
CloudNet์์ ์ฃผ๊ดํ๋ KANS(Kubernetes Advanced Networking Study)์ผ๋ก ์ฟ ๋ฒ๋คํฐ์ค ๋คํธ์ํน ์คํฐ๋์ ๋๋ค. ์๋์ ๊ธ์ ์คํฐ๋์ ๋ด์ฉ์ ๊ธฐ๋ฐ์ผ๋ก ์์ฑํ์ต๋๋ค.์คํฐ๋์ ๊ด์ฌ์ด ์์ผ์ ๋ถ์ CloudNet Blog๋ฅผ ์ฐธ๊ณ ํด์ฃผ์ธ์.
๋ค์ด๊ฐ๋ฉฐ
์ปจํ ์ด๋ ๊ธฐ๋ฐ์ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ด์ํ๊ณ ์๋ค๋ฉด, CI ๊ณผ์ ์์ ์ปจํ ์ด๋ ์ด๋ฏธ์ง ๋น๋๊ฐ ํ์ํ๋ค. ์ฌ๊ธฐ์๋ Docker ๊ธฐ๋ฐ์ผ๋ก ์ด๋ฏธ์ง๋ฅผ ๋น๋ํ๋ ๋ฐฉ๋ฒ์ ์์๋ณธ ํ ์ง์ ์์ ํด๋ณด๋ฉฐ ๋น๋ ๊ณผ์ ์์ ๋ฐ์ํ ์ ์๋ ๋ฌธ์ ์ ์ ์ดํด๋ณธ๋ค. ๋ง์ง๋ง์ผ๋ก Kaniko์ ๊ฐ์ ๋์ฒด๋ฐฉ์์ ์ดํด๋ณด๋ฉฐ ๋ง๋ฌด๋ฆฌํ๋ค.
Docker CI
Docker CI๋ฅผ ์ํด์ ์ปจํ ์ด๋ ๋ด๋ถ์์ Docker๋ฅผ ์ฌ์ฉํ ์ ์์ด์ผํ๋ค. ์ด๋ฅผ ์ํด DinD(Docker in Docker) ํน์ ์ปจํ ์ด๋ ๋ด๋ถ์์๋ docker๋ฅผ ์กฐ์ํ ์ ์๊ฒ socket ๋ง์ดํธ๊ฐ ํ์ํ๋ค.
DinD๋ ์ปจํ ์ด๋ ๋ด๋ถ์์ Docker ์ปจํ ์ด๋๋ฅผ ์คํํ๋ ๊ฒ์ ์๋ฏธํ๋ค. ์๋ฅผ ๋ค์ด, GitLab Runner๋ ๊ธฐ๋ณธ์ ์ผ๋ก ์ปจํ ์ด๋๊ธฐ๋ฐ์ด๋ค.
docker๋ socket ๊ธฐ๋ฐ์ผ๋ก ํต์ ํ๋ค. ๊ทธ๋ ๊ธฐ์ docker๊ฐ ์ฌ์ฉํ๋ socket์ ๋ง์ดํธํ๋ฉด, docker์ ํต์ ํ ์ ์๋ค. ์ฐ์ docker.socket์ ๋ํด ์ดํด๋ณด์.
socket
docker๋ Unix domain-socket์ ์ฌ์ฉํ๋ค. socket์ ํตํด docker daemon๊ณผ ํต์ ํ๋ฏ๋ก Host์ ์๋ docker socket์ ์ฌ์ฉํ ์ ์๋ค๋ฉด, ์ปจํ ์ด๋ ๋ด๋ถ์์๋ docker๋ฅผ ์ฌ์ฉํ ์ ์๋ค.
tcp๋ฅผ ์ฌ์ฉํ๊ฒ ์ค์ ํ๋ฉด, ๋ค๋ฅธ ์๋ฒ์์๋ docker ๋ฐ๋ชฌ์ ์ฌ์ฉํ ์ ์์ง๋ง k8s๋ผ๋ ์ข์ ๋ฐฉ๋ฒ์ด ์๋ค.
1
2
3
4
lsof /run/docker.sock
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
systemd 1 root 40u unix 0xffff966d51faddc0 0t0 31848 /run/docker.sock type=STREAM
dockerd 3966 root 4u unix 0xffff966d51faddc0 0t0 31848 /run/docker.sock type=STREAM
DinD vs mount docker.socket
์ด์ ๋ณธ๊ฒฉ์ ์ผ๋ก DinD์ socket ๋ง์ดํธ ๋ฐฉ์์ ๋น๊ตํ์. docker community์ jpetazzo Post์์ DinD์ docker.socket mount์ ์ฐจ์ด๊ฐ ์ ์ ๋ฆฌ๋์ด์๋ค. ๊ฐ๋จํ๊ฒ ์์ฝํ๋ฉด, ์๋์ ๊ฐ๋ค.
--privileged
๋ก ์ธํด SElinux์ ๊ฐ์ ๋ณด์ ๋ชจ๋๊ณผ ์ถฉ๋ํ ์ ์๋ค.๋ด๋ถ Docker๋ COW ์์คํ ์์์ ์คํ๋๊ธฐ์ ์ฌ๋ฌ ํ์ผ์์คํ ์กฐํฉ์ด ์๋๋์ง ์์ ์ ์๋ค.
ex) AUFS on top of AUFS
- ๋์ปค ์ปจํ ์ด๋์์ ์ปจํ ์ด๋๋ฅผ ์์ฑํด๋, Host Docker์์ ์ปจํ ์ด๋๊ฐ ์์ฑ๋๋ฏ๋ก ์บ์ ํจ์จ์ฑ์ด ๋์์ง๋ค.
๊ฒฐ๋ก ์ ์ผ๋ก ๋ณด์์, ์ฑ๋ฅ ์ฐจ์์์ DinD๋ณด๋ค socket mount ๋ฐฉ์์ด ๊ฐ๋ ฅํ ์ถ์ฒ๋๋ค. ์ด์ socket mount ๋ฐฉ์์ผ๋ก ์ปจํ ์ด๋์์์ ์ด๋ฏธ์ง๋ฅผ ๋น๋ํ๋ ๋ฐฉ๋ฒ์ ์์๋ณด์.
์ค์ต
ํ๊ฒฝ
Jenkins๋ฅผ ์ปจํ ์ด๋ ๊ธฐ๋ฐ์ผ๋ก ๊ฐ๋ํ๊ณ , ํด๋น ์ปจํ ์ด๋์ socket์ ๋ง์ดํธํ์ฌ ์ปจํ ์ด๋ ์ด๋ฏธ์ง๋ฅผ ๋น๋ํ๋ค.
- Jenkins ์ปจํ ์ด๋ ์์ฑ
1
docker run -d -p 8080:8080 -p 50000:50000 --name jenkins-server --restart=on-failure -v jenkins_home:/var/jenkins_home -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker jenkins/jenkins
์๋์ ๋ช ๋ น์ด๋ฅผ ์ํํ๋ฉด Init ๋น๋ฐ๋ฒํธ๋ฅผ ํ์ธํ ์ ์๋ค.
1
docker exec -it jenkins-server cat /var/jenkins_home/secrets/initialAdminPassword
- docker api ๊ฐ๋ฅ์ฌ๋ถ ํ์ธ
docker api ์ฌ์ฉ๊ฐ๋ฅ์ฌ๋ถ ํ์ธํด๋ณด๋ฉด ์ ์์ ์ผ๋ก ์ฌ์ฉํ ์ ์๋ ๊ฒ์ ์ ์ ์๋ค.
1
2
3
docker exec -it --user 0 jenkins-server docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
86170af3b1ab jenkins/jenkins "/usr/bin/tini -- /uโฆ" 44 seconds ago Up 43 seconds 0.0.0.0:8080->8080/tcp, :::8080->8080/tcp, 0.0.0.0:50000->50000/tcp, :::50000->50000/tcp jenkins-server
- ์ด์ root๊ฐ ์๋ jenkins ์ ์ ๋ docker๋ฅผ ์คํํ ์ ์๋๋ก ๊ถํ์ ๋ถ์ฌํ๋ค.
1
2
3
4
docker exec -it --user 0 jenkins-server bash
root@8266345972f8:/# groupadd -for -g $(stat -c '%g' /var/run/docker.sock) docker
root@8266345972f8:/# usermod -aG docker jenkins
์ค์ Jenkins ์ ์ ๋ก ์ ์ํ์ฌ API๋ฅผ ํ์ธํ๋ค.
1
2
3
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8266345972f8 jenkins/jenkins "/usr/bin/tini -- /uโฆ" 1 hours ago Up 1 hours 0.0.0.0:8080->8080/tcp, :::8080->8080/tcp, 0.0.0.0:50000->50000/tcp, :::50000->50000/tcp jenkins-server
์๋ฒ์ IP์ฃผ์์ 8080 ํฌํธ๋ก ์ ์ํ๋ฉด, ์๋์ ๊ฐ์ด Jenkins๋ฅผ ํ์ธํ ์ ์๋ค.
์ ํจ์ค CI
docker pipeline pulgin์ ์ค์น ํ ์๋์ ๊ฐ์ ๊ฐ๋จํ ํ์ดํ๋ผ์ธ์ ๋ง๋ ๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
pipeline {
agent any
stages {
stage('Create Dockerfile and a.txt') {
steps {
script {
// a.txt ํ์ผ ์์ฑ
writeFile file: 'a.txt', text: "This is a simple file."
// ๊ฐ๋จํ Dockerfile ์์ฑ
writeFile file: 'Dockerfile', text: """
FROM alpine:latest
COPY a.txt /a.txt
CMD ["cat", "/a.txt"]
"""
}
}
}
stage('Build Docker Image') {
steps {
script {
// Docker ์ด๋ฏธ์ง ๋น๋
docker.build("simple-jenkins-image")
}
}
}
stage('Run Docker Image') {
steps {
script {
// ๋น๋๋ ์ด๋ฏธ์ง๋ฅผ ์คํํ๊ณ a.txt ํ์ผ์ ๋ด์ฉ์ ์ถ๋ ฅ
docker.image("simple-jenkins-image").inside {
sh 'cat /a.txt'
}
}
}
}
}
post {
always {
echo 'Cleaning up...'
// ํ์ ์ ํด๋ฆฐ์
์ฝ๋ ์ถ๊ฐ
}
success {
echo 'Build completed successfully!'
}
failure {
echo 'Build failed!'
}
}
}
Job์ ์์ํ๋ฉด ์๋์ ๊ฐ์ด ์ ๋์ํ๋ค.
๋, ํธ์คํธ ์๋ฒ์์ ์ด๋ฏธ์ง ํ์ธํ ์ ์๋ค.
1
2
3
4
5
root@MyServer:~# docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
simple-jenkins-image latest f6943e3910a7 59 seconds ago 7.8MB
jenkins/jenkins latest fd13cb1b7315 2 days ago 471MB
alpine latest 324bc02ae123 5 weeks ago 7.8MB
๋์ ๋ฐฉ๋ฒ
์ปจํ ์ด๋ ์ด๋ฏธ์ง ๋น๋์ Docker ์์ฒด๋ฅผ ์ฌ์ฉํ์ง ์๋ ๋ฐฉ์์ด ์กด์ฌํ๋ค.
kaniko
DinD๋ ๋์ปค์์ฒด์์ sudo ๊ถํ์ด ํ์ํ๋ฏ๋ก, ์ปจํ ์ด๋๊ฐ root ๊ถํ์ ๊ฐ์ง๋ฉฐ ์ด๋ ๋ณด์์ ์ข์ง ์๋ค. ์ด๋ฅผ ์ํด Kaniko๊ฐ ๋์ค๊ฒ๋๋ค. ๋ฃจํธ ๊ถํ์์ด, ์ปจํ ์ด๋ ์ด๋ฏธ์ง๋ฅผ ๋น๋ํ ์ ์๋ ๋๊ตฌ์ด๋ค. ๊ตฌ๊ธ์์ ๋ง๋ค์์ผ๋ฉฐ, ์๋์ฐ ์ปจํ ์ด๋ ์ด๋ฏธ์ง๋ ์ง์ํ์ง ์๋๋ค๊ณ ํ๋ค.
์ถ์ฒ: https://blog.nashtechglobal.com/deep-dive-into-kaniko-understanding-the-architecture-and-workflow/
GitLab์ผ๋ก CI/CD๋ฅผ ๊ตฌ์ฑํ์ ๋, DinD๋ root ๊ถํ์ด ํ์ํ์ฌ ๋ณ๋๋ก Runner์ privileged ์ต์ ์ ํ์ฉํด์ค์ผ ํ๋ค. ์ด๋ ๋ณด์์ ์ข์ง ์๊ณ , GitLab์์๋ ์ด๋ฅผ ๊ถ์ฅํ์ง ์๋๋ค. ๋ณ๋๋ก Kaniko์ ๋ํ GitLab ๊ฐ์ด๋ ๋ฌธ์๊ฐ ์๋ค.
๋ ๋ค๋ฅธ ๋น๋๋๊ตฌ๋ก๋ Buildkit, Buildah๊ฐ ์๋ค๊ณ ํ๋ค. ํด๋น ํฌ์คํธ์์ ์ ์ ๋ฆฌ๋์ด์๋ค.