꾸물꾸물 졔의 개발공부
[CI/CD] CI/CD 매뉴얼 (Jenkins, Docker, React, Django) 본문
SSAFY 때 Django, React, Nginx를 이용하여 CI/CD를 구축했던 경험에 바탕하여 작성.. 언젠가 또 할 수도 있으니 ?
(한참이 지난 후에 복기하는 거라 캡쳐화면 없음 주의, 말만 구구절절)
배포 환경을 구축하기 위해서는 서버용 PC가 필요하다. 싸피에서는 EC2를 직접 생성해서 Pem key와 함께 지원해주시기 때문에 지원받은 EC2 사용, SSH 연결로 Ubuntu 접속 후 시작.
🌟EC2란? : AWS에서 제공하는 클라우드 컴퓨팅 서비스, 쉽게말해, 아마존으로 부터 한대의 컴퓨터를 빌리는 것
✔️전체 배포 과정은 다음과 같다.
1. GitLab에서 Master branch 로의 Push Event가 발생하면
2. Jenkins에서 WebHook을 통해 자동으로 빌드를 실행
3. Jenkins에서 각각의 React(Nginx), Django 프로젝트 내부에 생성한 DockerFile를 이용하여 Dockerimage 생성(tar압축파일)
4. Jenkins에서 SSH 연결을 통해 AWS Docker Container 생성
5. 외부에서 접속 : 도커 컨테이너에 올라간 Nginx에서 React와 Django를 각각 '/' , '/api/로 구분지어 연결
Docker 설치
Ubuntu 환경이 준비되었다면, Docker를 먼저 설치한다.
도커 설치를 위해 필요한 패키지들을 설치한다.
👉 업데이트 및 사전 패키지 설치
sudo apt update
sudo apt-get install -y ca-certificates \
curl \
software-properties-common \
apt-transport-https \
gnupg \
lsb-release
👉 gpg 키 다운로드
도커를 설치하기 위해서는 gpg key를 다운받아야 한다. 이는 리눅스 패키지 툴이 프로그램 패키지가 유효한 것인지 확인하기 위해 설치 전 gpg key를 통해 검증하는 과정을 거치기 때문이다.
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
👉 Docker 설치
docker 를 설치하고 추가적으로 docker-compose도 설치한. (Jenkins 설치할 때 편하게 하기 위해서.)
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-compose
Jenkins 설치 및 계정 생성
👉 docker-compose 이용해서 젠킨스 컨테이너 생성
docker에 Jenkins를 이미지를 이용해 (컨테이너) 설치하기 위해 우선 docker-compose.yml 파일을 생성한다.
vim docker-compose.yml
docker-compose.yml
version: '3'
services:
jenkins:
image: jenkins/jenkins:lts
container_name: jenkins
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /jenkins:/var/jenkins_home
ports:
- "9090:8080"
privileged: true
user: root
docker-compose.yml 파일을 다 작성하였으면, esc 누르고 :wq 입력하여 파일 저장 후 나오기.
- services : 컨테이너 서비스
- jenkins : 서비스 명
- image : 컨테이너 생성 시 사용할 image, jenkins의 lts 버전 사용
- container_name : 컨테이너 명
- volumes : 각 폴더 연결
- ports: 포트 매핑, aws의 9090 포트와 컨테이너의 8080 포트 연결 , 서버공인IP:9090 으로 접속하면 컨테이너 내부의 8080으로 포트포워딩 되어서 Jenkins 접속 가능
- privileged : 컨테이너 시스템의 주요 자원에 연결할 수 있게 하는것, 기본적으로 false 값 가짐
- user : 젠킨스에 접속할 유저 계정 (root : 관리자)
docker-compose.yml로 정의한 (Jenkins) 컨테이너를 실행, 개시
sudo docker-compose up -d
컨테이너가 성공적으로 올라갔는지 확인
sudo docker ps
👉 젠킨스 계정 생성 및 플러그인 설치
서버 공인 IP:9090 포트로 접속하면 젠킨스 시작화면이 나타나게 된다. Administrator password를 입력하라는 창이 나오는데 sudo docker logs jenkins 명령어를 통해 다음과 같은 값을 찾아내어 입력해준다.
이후, Install suggested plugins (기본 플러그인 자동 설치) 을 클릭하여 플러그인이 설치되는 것을 기다린다. 플러그인이 모두 설치되면 계정을 생성하여 젠킨스를 시작한다.
1. 젠킨스 메인화면 왼쪽에 있는 Jenkins 관리 탭을 클릭한다.
2. System Configuration > 플러그인 관리 로 이동한다.
3. 상단에 설치 가능 탭을 눌러 gitlab 을 검색한다. [GitLab, Generic Webhook Trigger, Gitlab API, GitLab Authentication] 을 모두 체크하고, 하단에 Install without restart 버튼을 클릭한다.
4. 같은 창에서 docker 검색, [Docker, Docker Commons, Docker Pipeline, Docker API] 체크 후, Install without restart
5. 같은 창에서 SSH 검색, [Publish Over SSH] 체크 후, Install without restart
모든 설치 성공적으로 완료 되었다면 끝 !
Jenkins 프로젝트 생성 WebHook 설정, 자동 빌드 테스트
모든 플러그인 설치까지 완료되었다면, 젠킨스에서 프로젝트를 생성하고, GitLab과 WebHook으로 연결하여 자동으로 빌드를 진행하는 것 테스트
👉 GitLab Repo 생성
프로젝트를 위해 깃랩에 리포지토리를 생성해두었고, Django와 React를 이용하여 배포를 진행할 것이기 때문에 각 폴더로 구분지어 생성하였다. Django : project_backend 폴더, React : project_frontend 폴더
👉 젠킨스 프로젝트 생성
1. Jenkins 메인 화면 왼쪽의 + 새로운 Item 클릭
2. 프로젝트 이름(ex)testproject)을 작성하고 Freestyle project 클릭 후 OK
3. 소스 코드 관리 탭의 라디오 버튼 중, Git 클릭
4. Repository URL에 Gitlab에 생성해 두었던 리포지토리 URL을 입력한다. ( 에러메시지 정상 O )
5. Credentials 에서 add → jenkins
6. Kind : Username with password 선택, 아래 정보 입력 후 Add 클릭
- Username : GitLab 아이디
- Password : 깃 비밀번호
- ID : Credential 구별하기 위한 아무 텍스트 입력
7. Credentials 에서 방금 만들었던 Credential을 선택했을 때 4에서 생긴 에러메시지가 사라지면 성공
8. 빌드 유발 탭에서 아래 부분 체크 후 생기는 고급 버튼 클릭
- 4번째에 있는 Build when a change is pushed to GitLab. ~~
- 하위 목록 중, 아래 4개 클릭
- Push Events
- Opened Merge Request Events
- Approved Merge Requests (EE-only)
- Comments
9. 스크롤을 아래로 내리면 Allowed branches - Allow all branches to trigger this job 선택
10. Secret token 의 Generate 버튼을 누르면 토큰 생성, Gitlab과 WebHook을 연결 할 때 사용되니 저장
11. Build 탭의 Build 부분에서 Add build step 클릭하고, Execute shell을 선택한다.
12. 그럼 명령어를 입력할 수 있는 창이 나타나는데, 연결 테스트만 하는 것이기 때문에 아무 텍스트 입력 후, 저장 버튼 눌러 저장
13. 저장 버튼을 누르면 프로젝트 화면으로 이동하는데, 지금 빌드를 눌러서 젠킨스 수동 빌드 진행
14. 완료 ✔️표시가 뜨면 성공, v을 눌러 Console Output을 확인해본다.
15. 12에서 입력해두었던 텍스트가 보이면서 SUCCESS로 끝났다면 성공
👉 GitLab WebHook 연결
배포할 프로젝트의 GitLab Repository 에서 왼쪽의 Setting - Webhooks 을 눌러 이동한다.
URL에는 http://배포서버공인IP:9090/project/생성한jenkins프로젝트이름/ 을 입력한다.
Secret token 에는 위 10번 과정에서 저장해둔 값을 입력한다.
Trigger로 Push events, Merge request events 를 설정한다. Push events 대상 Branch는 master로 설정한다.완료했다면 Add Webhook 버튼을 눌러 webhook을 생성한다.
WebHook을 생성하고 나면 빌드 테스트를 위해 webhook 이 생성되는데 Test - Push events 를 선택한다. Hook executed successfully : HTTP 200 알람이 뜨면 응답이 잘 넘어온 것이다. (+젠킨스에서도 정상 빌드 확인 가능)
💡여기까지가 Jenkins와 Gitlab을 연결한 것이다. 연결된 GitLab Respository의 master branch 에 push 이벤트가 발생하면, 젠킨스에서는 자동으로 빌드를 수행하게 된다.
Jenkins와 연결된 GitLab 프로젝트로 도커 이미지 빌드
젠킨스에서 도커(React/Django) 빌드를 하기 위해서는 위에서 만든 젠킨스 컨테이너 안에 도커를 설치해야 한다. EC2에 도커를 설치했을 때와 동일한 방법으로 진행한다.
젠킨스에 도커를 설치할 것이기 때문에 우선 젠킨스 bash shell 에 접근한다.
sudo docker exec -it jenkins bash
정상적으로 접속이 되었다면 해당 환경에 docker를 다시 설치한다.
👉 업데이트 및 사전 패키지 설치
apt update
apt-get install -y ca-certificates \
curl \
software-properties-common \
apt-transport-https \
gnupg \
lsb-release
EC2에 docker를 설치했을 때와 다른점은, root 계정으로 젠킨스에 접속했기 때문에 여기에서는 sudo 명령어를 지운다.
👉 gpg 키 다운로드
mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \
$(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
gpg 키를 다운받을 때 젠킨스 컨테이너 내부에 설치된 os를 확인해보는 것이 좋다. cat /etc/issue 명령어를 통해 os를 확인해보면 debian으로 나타나는 것을 확인할 수 있다. 만약 os가 ubuntu로 표시된다면 명령어에서 debian인 부분을 ubuntu 로 바꿔주면 된다. 이 부분을 맞춰주지 않으면 패키지를 찾지 못하는 에러가 발생한다.
👉 Docker 설치
apt update
apt install docker-ce docker-ce-cli containerd.io docker-compose
💡여기까지 완료한다면, Jenkins Container 내부에도 Docker 설치가 완료된다.
👉 Gitlab 프로젝트에 DockerFile 작성
GitLab의 각 프로젝트 폴더(Django : project_backend 폴더, React : project_frontend 폴더)에 DockerFile을 만들고 명령어 입력 후 저장한다.
Django Project DockerFile
FROM python:3.9.5
WORKDIR /var/jenkins_home/workspace/testproject/project_backend
COPY requirements.txt ./
RUN pip install --upgrade pip
RUN pip install -r requirements.txt
COPY . .
CMD ["gunicorn", "project_backend.wsgi", "--bind", "0.0.0.0:8080"]
Django DockerFile에서는 Python 3.9.5 이미지를 베이스로 두고, Requirements를 통해 pip 패키지를 설치한 후, 프로젝트 폴더를 이미지에 복사한다. 그 후 cmd를 통해 컨테이너를 실행한다.
WORKDIR /var/jenkins_home/workspace/젠킨스프로젝트명/깃랩폴더명
React Project DockerFile
FROM node:16.15.0 as build-stage
WORKDIR /var/jenkins_home/workspace/testproject/project_frontend
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
FROM nginx:stable-alpine as production-stage
COPY --from=build-stage /var/jenkins_home/workspace/testproject/project_frontend/build /usr/share/nginx/html
#COPY --from=build-stage /var/jenkins_home/workspace/testproject/project_frontend/deploy_conf/nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g","daemon off;"]
- FROM : 베이스 이미지 설정
- WORKDIR : 작업 디렉토리 설정 /var/jenkins_home/workspace/젠킨스프로젝트명/깃랩폴더명
- COPY : 파일 복사 <Host 파일 경로> <Docker 이미지 파일 경로>
- RUN : 명령 실행
- CMD : 컨테이너 실행 명령
- EXPORT : 포트 익스포트
React DockerFile에서 #으로 주석처리한 부분은 nginx 설정에 사용되는 부분이다. 이후 nginx 설정 시에 변경.
👉 젠킨스에서 DockerFile 이용하여 도커 이미지 생성
Django와 React 각각의 DockerFile을 완성했다면, 젠킨스에서 이 도커파일을 이용해 이미지를 생성한다.
1. 젠킨스 프로젝트 페이지에서 왼쪽의 구성 버튼 클릭
2. Build 탭으로 이동하여, Execute Shell의 아까 프로젝트 생성시 연결 테스트를 위해 아무 텍스트나 입력해두었던 것을 다음 명령어로 바꿔준다.
docker image prune -a --force
mkdir -p /var/jenkins_home/images_tar
cd /var/jenkins_home/workspace/testproject/project_frontend/
docker build -t react .
docker save react > /var/jenkins_home/images_tar/react.tar
cd /var/jenkins_home/workspace/testproject/project_backend/
docker build -t django .
docker save django > /var/jenkins_home/images_tar/django.tar
ls /var/jenkins_home/images_tar
도커 이미지 압축파일을 저장할 폴더(images_tar)를 생성 한 후, react와 django 각 프로젝트의 위치로 이동 후 작성해두었던 DockerFile을 이용해 도커 이미지를 빌드한다. 도커 이미지를 react.tar , django.tar 로 압축하여 생성해두었던 폴더에 저장.
ls /var/jenkins_home/image_tar : 해당 폴더에 있는 파일 목록 출력(잘 압축되어 저장되었는지 확인)
명령어를 변경하였다면 저장 버튼을 눌러준다.
3. 지금 빌드 버튼을 눌러 빌드 실행
4. 빌드 완료 표시 및 빌드에 성공 SUCCESS 했다면 완료
Jenkins에서 SSH 명령어 전송을 통해 빌드한 도커 이미지를 베이스로 컨테이너 생성 (🌟기본 배포 완료)
EC2 생성시 사용한 Pem key 가 필요하다 (싸피제공)
👉 젠킨스 SSH 연결 설정 (Publish over SSH)
젠킨스에서 AWS로 SSH 명령어를 전송하기 위해 AWS 인증키인 pem key를 등록해주어야 한다.
1. 젠킨스 메인 페이지에서 (왼쪽의)Jenkins 관리 > 시스템 설정 클릭
2. 스크롤을 아래로 내리다 보면 Publish over SSH 항목이 있다. 여기서 SSH Servers 추가 버튼 클릭
3. 아래의 form 을 채우고, 고급 버튼을 클릭
- Name : 그냥 이름 텍스트
- Hostname : 서버공인IP
- Username : EC2 접속 계정 이름
4. Use password authentication, or use different key 를 체크한다. 그러면 하단에 form 이 생성되는데, Key에만 값을 입력한다.
5. Pem key 파일을 vscode 또는 메모장으로 open 하여 전체 내용을 복사 붙여넣기 한다.
6. 이후 Test Configuration 버튼을 눌렀을 때 Success가 나오면 성공이다.
단, ubuntu 버전이 18.xx버전보다 높은 경우에 pem 키로 인증이 실패하는 경우가 생길 수 있다. 이 경우에는 Pem 키로 인증하는 것이 아닌, ubuntu 계정의 비밀번호를 설정하여 연결하는 방법을 사용하면 해결된다.
👉 SSH 연결 오류 해결 방법 (Ubuntu 계정 비밀번호)
가장 먼저, root 계정 비밀번호를 설정한다.
sudo password
비밀번호를 설정했으면, su - 명령어를 입력하여 root 계정으로 접속한다. 이후, passwd ubuntu 명령어를 통해 ubuntu 계정의 비밀번호를 설정한다. 복잡한 비밀번호 하는 것 추천
su -
passwd ubuntu
<passwd : password updated successfully> 라는 문구가 뜨면 성공, 패스워드 설정이 완료되었다면 EC2에 id,pw를 이용한 로그인을 허용해주어야 한다. (기본적으로는 차단 되어 있음) root 계정으로 접속되있는 상태에서 vim /etc/ssh/sshd_config 명령어를 통해 sshd_config 파일을 열어준다.
vim /etc/ssh/sshd_config
vim 편집기가 열리고, 아래로 내리다보면 PasswordAuthentication no 라고 적힌 부분을 찾을 수 있다. 해당 부분의 no를 yes 로 바꾸고 esc :wq 를 입력하여 저장 후 나온다.
sshd 재시작
service sshd reload
이렇게 생성한 Ubuntu 계정의 비밀번호를 다시 젠킨스로 돌아와 입력하면 된다.
Key 부분에 붙여넣기 하였던 pem key 는 모두 지우고, Passphrase / Password 칸에 ubuntu 계정의 비밀번호를 입력해준다. 다시 Test Configuration 버튼을 누르면 성공적으로 Success 가 뜬다.
💡(pem key/ubuntu pwd 둘 중 하나의 방식으로든) SSH 연결이 완료 되었다면 저장.
👉 젠킨스 '빌드 후 조치'로 SSH 명령어 전송 (EC2에 도커 컨테이너 생성)
1. 젠킨스 프로젝트 페이지에서 구성 버튼 클릭
2. 빌드 후 조치 탭에서, 빌드 후 조치 추가 클릭 : Send build artifacts over SSH 를 선택한다.
Source files 은 컨테이너에서 aws 로 파일을 전송하는 부분이다. 무의미하지만 필수 입력 사항이기 때문에 아무거나 입력해도 무방하다. (ex) /README.md) Exec command 부분에는 아래 명령어를 복붙해준다.
sudo docker load < /jenkins/images_tar/react.tar
sudo docker load < /jenkins/images_tar/django.tar
if (sudo docker ps | grep "react"); then sudo docker stop react; fi
if (sudo docker ps | grep "django"); then sudo docker stop django; fi
sudo docker run -it -d --rm -p 80:80 -p 443:443 --name react react
echo "Run project_frontend"
sudo docker run -it -d --rm -p 8080:8080 --name django django
echo "Run project_backend"
이전에 압축하여 폴더에 저장해두었던 압축파일을 해제하여 docker 이미지로 등록한다. 만약 react 또는 django 컨테이너가 동작중이라면 stop. 이후 컨테이너를 각 포트로 연결하여 생성한다. 컨테이너 명은 react 와 django
3. 저장 후 지금 빌드 버튼을 눌러 빌드 실행
4. 콘솔에서 SUCCESS 확인
🌟여기까지 성공한다면, 서버의 80포트에서는 React를 8080포트에서는 Django를 서비스 하게 된다.
Nginx를 통해 React와 Django 경로 설정
nginx 설정은 굳이 하지 않아도 위 과정까지만 하면 서버는 잘 서비스 되어진다.
하지만 이 과정을 하지 않으면 HTTPS 설정을 할 경우 번거로운 작업이 생길 수 있다. 만약 React 부분은 HTTPS 설정에 성공하였는데 Django(백엔드)가 적용에 실패한다면, https → http 의 크로스 도메인 오류 때문에 백엔드 API가 호출되지 않을 수도 있다.
따라서 하나의 도메인, 하나의 Port에서 두 서비스를 구분지어 호출할 수 있는 부분이 필요하다.
마지막 Nginx 설정을 하게 되면, 기존에 80포트로 react에 접속하고 8080포트로 django에 접속하던 서비스를, 80 포트를 통해 react와 django 모두 접속할 수 있게 변경시킨다. (경로로 구분)
👉 nginx.conf 파일 생성
Ubuntu 콘솔에서 다음 명령어를 작성한다.
cd /jenkins/workspace/젠킨스프로젝트명/깃랩react폴더명
sudo mkdir deploy_conf
cd deploy_conf
sudo vim nginx.conf
- 프론트 react 프로젝트 디렉토리로 이동 (cd /jenkins/workspace/testproject/project_frontend)
- deploy_conf 라는 새로운 디렉토리를 생성하고 해당 위치로 이동
- Nginx 설정을 위한 nginx.conf 파일을 작성하고 편집기로 이동하여 파일 작성
nginx.conf 파일
upstream backend{
ip_hash;
server 'PRIVATE_IP':8080;
}
server {
listen 80;
listen [::]:80;
server_name localhost;
#access_log /var/log/nginx/host.access.log main;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
location /api {
proxy_pass http://backend/;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}
3번째 줄의 server 'PRIVATE_IP':8080 부분은 EC2 인스턴스의 Private ip 주소를 입력하면 된다.
upstream을 통해서 backend를 로컬 ip:8080 주소(현재 django 포트)와 연결시키고,
해당 주소를 location /api에 연결.
기존 react 프로젝트는 location / 에 연결된다.
🌟결과적으로 공인ip주소/api 로 요청하게 되면 Nginx에서 해당 nginx.conf 파일 설정에 따라 Django 서버로 연결시켜주게 된다. 즉, Nginx와 Django 서버 사이의 통신은 로컬에서 이루어지기 때문에 공인IP를 따로 등록할 필요가 없다.
따라서 가장 처음에 EC2에서 Django 서비스를 위해 허용했던 8080포트를 막게 된다면, 외부에서 장고 서버로는 직접 접속할 수 없고 nginx(80포트)를 통해서만 접속할 수 있게 된다.
nginx.conf 파일 작성을 마쳤다면 esc , :wq 를 통해 파일을 저장한다.
👉 DockerFile 수정
앞서 깃랩의 react 부분(project_frontend)에 작성해두었던 DockerFile을 수정한다. #로 주석처리 해두었던 부분 주석해제
FROM node:16.15.0 as build-stage
WORKDIR /var/jenkins_home/workspace/testproject/project_frontend
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
FROM nginx:stable-alpine as production-stage
COPY --from=build-stage /var/jenkins_home/workspace/testproject/project_frontend/build /usr/share/nginx/html
COPY --from=build-stage /var/jenkins_home/workspace/testproject/project_frontend/deploy_conf/nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g","daemon off;"]
방금 생성한 nginx 설정 파일(=nginx.conf)을 nginx 이미지로 옮기는 명령어다.
👉 최종 빌드 테스트
도커파일의 수정사항을 반영시키기 위해서 gitlab에 push Event 를 발생시켜주어야 한다. 또한 최종적으로 모든 기능이 잘 작동하는지 테스트 하기 위해 수정사항을 master Branch 로 push 한다.
모든 빌드가 완료되고 SUCCESS 했다면,
http://공인ip/ 로 접속하면 React ,
http://공인ip/api 로 접속하면 Django 가 서비스된다.
추가적으로 EC2 인바운드규칙에서 8080포트 접속을 차단한다면, http://공인ip:8080 으로는 Django에 접속할 수 없다.
'DevOps > CI|CD' 카테고리의 다른 글
[CI/CD] CI/CD란? - 지속적 통합/지속적 배포 (0) | 2023.03.30 |
---|