진행 상황
백엔드와 프론트 서버가 배포되어 있는 상태입니다. CICD가 완료되었고, 결과는 mm을 통해 전송됩니다.
현재 rest docs 서버 설정 부분 작업 중입니다.

시스템 구성
- jenkins 2.3
- blueocean : 지속적 배포와 관리 할 수 있는 UI를 지원합니다.
- docker
- docker-compose - jenkins
- DB 서버 : aws s3
- 파일 서버 : aws rds
jenkinsfile은 Declarative Pipeline 방식으로 구현했습니다.
jenkins blueocean과 front + nginx, backend 도커 서버로 구축되어 있습니다.
주의사항
빌드할 때 시간이 걸립니다(5분정도??) 충돌을 막기 위해 머지할 때 최소 5~10분 정도의 간격을 두고 해야합니다.
mm을 통해 빌드 상황을 알려주고 있으며 jenkins-log 채널의 최근 메세지가 started 상태라면 기달려야합니다.
충돌 시, 우분투에서 해당 도커 컨테이너 삭제(jenkins 말고 backend, frontend 같은거)하고 이미지 삭제해준 후 다시 트리거 실행시키거나 푸시하면 해결됩니다.
Docker 명령어
- 도커 컨테이너 보는 법
- sudo docker ps -a
- 도커 컨테이너 삭제
- sudo docker rm <container_id>
- 도커 이미지 보는 법
- sudo docker images
- 도커 이미지 삭제
- sudo docker rmi <image_id>
- 강제 옵션 -f (삭제가 안될 때,)
- ex) docker rmi -f <image_id>
- 도커 로그 확인
- docker logs <image_id> : 도커가 꺼져있어도(컨테이너가) 최종적으로 실행된 기록을 가지고 있습니다(오류 잡을 때, 매우 유용)
- 그 외, 빌드할 때, run에서 애러가 나면! -> jenkinsfile의 run 부분의 stop과 remove 부분 코드를 서버에서 직접 하나씩 복붙해서 제거합니다. 만약 제거에 오류가 있을 경우, 강제옵션을 사용해서 수동으로 제거해줍니다. 그리고 다시 빌드합니다.
배포과정
기본 설치
sudo apt-get update sudo apt-get install nodejs sudo apt-get install npm # 설치 확인 nodejs -v npm -v
docker 설치
# 필수 패키지 설치 sudo apt-get install apt-transport-https ca-certificates curl gnupg-agent software-properties-common # GPG Key 인증 curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - # docker repository 등록 sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" # 도커 설치 sudo apt-get update && sudo apt-get install docker-ce docker-ce-cli containerd.io # 시스템 부팅시 도커 시작 sudo systemctl enable docker && service docker start # 도커 확인 sudo service docker status
예전에 rds를 쓸지 결정이 안된 상태에서 작성한 거라 다음 구분 선까지 넘어가셔도 괜찮습니다.
Mysql 도커 설치
# mysql 이미지 불러오기 sudo docker pull mysql # 도커 이미지 확인 sudo docker images # 도커 이름은 --name 뒤에 넣고, password는 root 패스워드(사용자 지정) sudo docker run -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=finalproject --name thankstore_mysql mysql # 도커 컨테이너 bash 접속 sudo docker exec -it thankstore_mysql bash # mysql 접속 mysql -u root -p # 패스워드 입력 finalproject
도커 컨테이너 mysql 접속 후
# DB 생성 create database 뭐라할까요; # 사용자 생성 CREATE USER 'thankstore'@'%' IDENTIFIED BY 'finalproject'; # 사용자 권한 부여 GRANT ALL PRIVILEGES ON *.* TO 'thankstore'@'%'; # 권한 새로고침(안해도 됨) flush privileges;
Https 키 발급
sudo apt-get install letsencrypt # 만약 nginx를 사용중이면 중지! sudo systemctl stop nginx # 인증서 발급 sudo letsencrypt certonly --standalone -d www제외한 도메인 이름 # 이메일 쓰고 Agree # 뉴스레터 no # 이제 인증서가 발급된다. 이 인증서를 잘보관하자 # 2가지 키가 발급되는데 이 두가지를 써야한다. 밑의 경로에 각각 하나씩 있다. # 마지막 부분에서 씀!!!! ssl_certificate /etc/letsencrypt/live/thankstore.click/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/thankstore.click/privkey.pem;
개인적으로 만든 aws의 도메인 이름은 letsencrypt 에서 발급 안됩니다!
ex) ec2-3-36-71-194.ap-northeast-2.compute.amazonaws.com
ssafy 프로젝트는 가능합니다
ex) j4f002.p.ssafy.io 이런거
젠킨스
- docker-compose.yml 파일 작성 - 젠킨스 도커 생성
version: '3.7' # docker -v 버전을 입력 services: # 실행하려는 컨테이너들을 정의 jenkins: # 서비스의 이름 image: 'jenkinsci/blueocean' # 사용할 도커 이미지 restart: unless-stopped # 명시적으로 중지되거나, Docker 자체가 중지되는 경우 재시작 user: root privileged: true # permission denied 관련 설정 ports: # 사용할 포트 번호 설정 - '9090:8080' volumes: # 로컬 디렉토리의 특정 경로를 컨테이너 내부로 마운트할 수 있음 - '/home/ubuntu/docker/jenkins-data:/var/jenkins_home' - '/var/run/docker.sock:/var/run/docker.sock' - '$HOME:/home' container_name: 'jenkins'
- 젠킨스 블루오션 실행
# 해당 디렉토리에서 도커 컴포즈 업! sudo curl -L "https://github.com/docker/compose/releases/download/1.24.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose # 권한 줘야합니다. $ sudo chmod +x /usr/local/bin/docker-compose sudo docker-compose up -d # 실행시킨 도커 컨테이너에 접속 sudo docker exec -it jenkins /bin/bash # 비밀번호 겟해야함. 젠킨스의 초기 비밀번호로 인터넷상에서 들어갈 수 있음. cat /var/jenkins_home/secrets/initialAdminPassword # 이 비밀번호로 http://도메인:9090 들어갈 수 있음.
젠킨스 환경 설정 및 GITLAB 웹훅 설정
- 들어가서 인증 후, 웹으로 들어가서 권장 설치 선택
- 아이디 비번 만들고, url에서 그냥 next! -> 젠킨스로 들어와짐.
- 왼쪽 매뉴 바에서 jenkins 관리->플러그인관리 -> 설치가능 탭에서 -> Gitlab Plugin / gitlab hook Plugin 설치
- jenkins 관리 → 시스템 설정에서 gitlab 관련 설정 추가

여기서 gitlab에 대한 토큰을 만들어야합니다. name에는 gitlab connection 이름을 지정하고, gitlab host url을 써주면 됩니다.(https://lab.ssafy.com)
credentials는 gitlab api token에서 입력합니다.
gitlab 토큰 얻는 방법
- gitlab에서 seettings 클릭

- Access Tokens 탭에 들어와서 이름 지정하고(날짜는 따로 지정안해도 됩니다.) 원하는 scopes 지정 후 토큰 발행.
- 그럼 상단에 토큰 번호 하나가 나옵니다. 이 토큰 값은 한 번밖에 안 보여주기 때문에 다른 곳에 저장해 놓으세요.

- 다시 원래 창으로 들어와서 토큰을 넣고 나머지 정보를 입력합니다.

- add 후, test connection를 눌러서 success가 뜨면 성공입니다!
- 메인화면에서 새로운 아이템(new item) 선택 -> 파이프라인 선택 후, 생성
- 생성된 item에 접속 후, 구성 클릭 -> 스크롤을 내려서 Pipeline을 찾으세요.
SCM -> git 선택. Repository URL은 Gitlab Repository URL 입력 .

- Credentials는 ADD 한 후, name 에는 id gitlab, password 는 패스워드 적고 생성해줍니다.

- 위로 스크롤 좀 올려서 이제 development에 머지가 될 때, 서버로 ci/cd가 될 수 있도록 웹훅 설정합니다.

- Build Triggers 고급... 클릭 후, include에서 webhook 브랜치 선택
우측 아래 Generate 클릭 - 키 생성됩니다. -> 소중히 보관하세요.

- 다시 깃랩의 설정으로 갑니다.
프로젝트 레포지토리에서 Settings → Integrations 선택
URL은 Build Triggers 설정 시 보였던 Gitlab Webhook URL 입력
Secret token은 Build Triggers 설정 시 생성했 던 Secret Token 입력
add webhook 후, 푸시 이벤츠해보고 success 확인.

그럼 젠킨스 환경 설정과 연결은 끝나게 됩니다!
도커 네트워크 설정
도커 네트워크란? : 같은 Docker Host내에서 실행중인 Container간 연결할 수 있도록 돕는 논리적 네트워크같은 개념입니다. 서로 간 통신을 가능하게 합니다.
sudo docker network create thxstorecicdnetwork;
위에서 말한 것처럼. 각 서버로 구성되어야할 폴더에 dockerfile과 전체적인 ci/cd과정을 관리해줄 수 있는 jenkinsfile이 루트 디렉토리에 들어가서 정상 작동만하면 끝입니다. dockerfile과 jenkinsfile 내용입니다.
Jenkinsfile
각 stage를 설정해서 pull 받아오는 과정(git pull), 빌드 과정(Docker build), 배포 과정(Docker run) 단계별로 나눠서 파이프라인을 작성했습니다.
여기는 mm webhook 부분이 빠져있습니다.
# 도커 파이프라인 pipeline { agent none options { skipDefaultCheckout(false) } stages { stage('git pull') { # pull 받아오는 상태 agent any steps { checkout scm } } stage('Docker build') { # docker build 상태 agent any steps { sh 'docker build -t frontend:latest /var/jenkins_home/workspace/jenkins-cicd/frontend' # frontend -t 는 생성할 이미지 이름. sh 'docker build -t backend:latest /var/jenkins_home/workspace/jenkins-cicd/backend' # backend 도커가 있는 위치. 빌드는 도커 이미지 파일을 만들어 주는 것입니다!! 아직 실행 X } } stage('Docker run') {# docker 배포 상태 agent any steps { # 도커 시작 전, 기존에 실행중인 도커를 멈추고 제거하는 작업. sh 'docker ps -f name=frontend -q \ | xargs --no-run-if-empty docker container stop' sh 'docker ps -f name=backend -q \ | xargs --no-run-if-empty docker container stop' # 컨테이너 제거 sh 'docker container ls -a -f name=frontend -q \ | xargs -r docker container rm' sh 'docker container ls -a -f name=backend -q \ | xargs -r docker container rm' # 도커 이미지 제거-> 도커 이미지 중 none tag의 id를 구해서 강제로 삭제하는 명령어 sh 'docker images -f dangling=true && \ # -f 강제, dangling=true, Docker 에서 none tag 삭제 docker rmi $(docker images -f dangling=true -q)' # 이미지 해시 제거, 이미지 제거. 사용되지 않은 모든 이미지 제거? -q 옵션 이미지 ID # -v 호스트 경로:컨테이너경로 연결 # 도커 실행 # frontend 이름으로, jenkinsnetwork에서 frontend:latest를, 포트는 포트(nginx) sh 'docker run -d --name frontend \ -p 80:80 \ -p 443:443 \ -v /home/ubuntu/sslkey/:/var/jenkins_home/workspace/jenkins-cicd/sslkey/ \ -v /etc/localtime:/etc/localtime:ro \ --network jenkinsnetwork \ frontend:latest' sh 'docker run -d --name backend \ --network jenkinsnetwork backend:latest' } } } }
jenkins와 mattermost webhook연동

통합에 들어가서 전체 incommig webhook을 선택합니다.
incoming webkook 추가를 클릭 후, 제목과 설명, 채널을 지정합니다.


생성이 되며, url 부분을 복사합니다.
젠킨스에 접속 후, 젠킨스 관리 -> 플러그인관리에서 mattermost notification plugin을 설치합니다.

다시 메인에서 젠킨스 관리 -> 시스템 설정으로 들어 간 후, Global Mattermost Notifier Settings라는 항목을 찾습니다.

이전에 mm에서 설정한 url과 사용하고 있는 build item을 적고 ,Test connection을 눌렀을 때, 설정한 채널에 메시지가 간다면 성공입니다. 이제 젠킨스파일 파이프라인에 추가하면 됩니다.
Jenkinsfile + mm webhook message (최종본)
pipeline { agent none options { skipDefaultCheckout(false) } stages { stage('git pull') { agent any steps { mattermostSend ( color: "#2A42EE", message: "Build STARTED: ${env.JOB_NAME} #${env.BUILD_NUMBER} (<${env.BUILD_URL}|Link to build>)" ) checkout scm } } stage('Docker build') { agent any steps { script { try { sh 'docker build -t frontend:latest /var/jenkins_home/workspace/thxstore-jenkins-cicd/frontend' sh 'docker build -t backend:latest /var/jenkins_home/workspace/thxstore-jenkins-cicd/backend' }catch(e) { mattermostSend ( color: "danger", message: "Build FAILED: ${env.JOB_NAME} #${env.BUILD_NUMBER} (<${env.BUILD_URL}|Link to build>)" ) } } } } stage('Docker run') { agent any steps { script { try { sh 'docker ps -f name=frontend -q | xargs --no-run-if-empty docker container stop' sh 'docker ps -f name=backend -q | xargs --no-run-if-empty docker container stop' sh 'docker container ls -a -f name=frontend -q | xargs -r docker container rm' sh 'docker container ls -a -f name=backend -q | xargs -r docker container rm' sh 'docker images -f dangling=true && docker rmi $(docker images -f dangling=true -q)' sh 'docker run -d --name frontend \ -p 80:80 \ -p 443:443 \ -v /home/ubuntu/sslkey/:/var/jenkins_home/workspace/thxstore-jenkins-cicd/sslkey/ \ -v /etc/localtime:/etc/localtime:ro \ --network thxstorecicdnetwork \ frontend:latest' sh 'docker run -d --name backend \ --network thxstorecicdnetwork backend:latest' }catch(e) { currentBuild.result = "FAILURE" } finally { if(currentBuild.result == "FAILURE"){ mattermostSend ( color: "danger", message: "Build FAILED: ${env.JOB_NAME} #${env.BUILD_NUMBER} (<${env.BUILD_URL}|Link to build>)" ) } else{ mattermostSend ( color: "good", message: "Build SUCCESS: ${env.JOB_NAME} #${env.BUILD_NUMBER} (<${env.BUILD_URL}|Link to build>)" ) } } } } } } }
메시지 관련 코드 (mattermostSend) 는 짧고 간단하기 때문에 따로 설명하지 않겠습니다. 여기서는 started, failed, success 3단계로 나눠서 메세지를 발생했습니다.
NGINX 파일 - homepage.conf(최종 - 사용시, 주석은 제거해주세요)
# homepage.conf # 포트 80 요청을 모두 잡아서 443으로 리다이렉션 server { listen 80 default_server; listen [::]:80 default_server; server_name k4b202.p.ssafy.io; return 301 https://$server_name$request_uri; #마지막으로 https요청 된 URI 의 버전으로 301 리디렉션을 반환합니다 . 이 server블록 에 도달하는 모든 요청 http은 포트 80 요청 만 수신하기 때문에입니다. ssl로 리다이렉션 } server { listen 443 ssl; listen [::]:443 ssl; root /home/ubuntu/s04p31b202/frontend/dist; # index 위치 # Add index.php to the list if you are using PHP index index.html index.htm index.nginx-debian.html; # index server_name k4b202.p.ssafy.io; client_max_body_size 50M; # 이미지나 gif 용량 등. # SSL 등록 ssl_certificate /var/jenkins_home/workspace/thxstore-jenkins-cicd/sslkey/fullchain.pem; ssl_certificate_key /var/jenkins_home/workspace/thxstore-jenkins-cicd/sslkey/privkey.pem; # root 등록 location / { # First attempt to serve request as file, then # as directory, then fall back to displaying a 404. alias /usr/share/nginx/html/homepage/; try_files $uri $uri/ /index.html; } # 백엔드 돌려줄 것 서버가 api 서버로 location /api { proxy_pass http://backend:8080; proxy_http_version 1.1; proxy_set_header Connection ""; # 도커 8080으로 만들 것. backend는 jenkins 파일에서 도커 이름으로 부여. 이쪽으로 돌리겠다 # proxy_set_header Connection : nginx가 proxy로 중계할 때, 중계받은 서버버에 request header를 재정의해서 전달 용도. 밑에와 정의., proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Port $server_port; } }
nginx share로 설정., https 설정. front docker 서버에 같이 들어갑니다.
Frontend dockerfile(최종 - 사용시, 주석은 제거해주세요)
FROM node:lts-alpine as build-stage WORKDIR /homepage # WORKDIR은 RUN, CMD, ENTRYPOINT의 명령이 실행될 디렉터리를 설정합니다. < 컨테이너 위치 # WORKDIR 뒤에 오는 모든 RUN, CMD, ENTRYPOINT에 적용되며, 중간에 다른 디렉터리를 설정하여 실행 디렉터리를 바꿀 수 있습니다. # 복사할 파일 경로 : 이미지에서 파일이 위치할 경로 COPY . . RUN npm install RUN npm run build # 가장 기본적인 커맨드이다. 어떤 이미지를 기반으로 새로운 이미지를 생성할 것인지를 나타낸다 FROM nginx:stable-alpine as production-stage # RUN 커맨드는 정말 간단하게 생각해서, bash 쉘에서 입력하는것과 동일하다고 생각하면 된다. RUN rm /etc/nginx/conf.d/default.conf COPY ./homepage.conf /etc/nginx/conf.d/homepage.conf COPY --from=build-stage ./homepage/dist /usr/share/nginx/html/homepage # COPY --from=builder를 통해 전 단계 스테이지 빌드에서 생성된 특정 결과물만 새로운 BASE 이미지로 복사해서 이미지를 생성했다. # '이 도커 이미지는 3000번 포트를 외부에 공개할 예정이다'라고 명시할 #EXPOSE 구문으로 명시한 포트는 'docker run -P' 명령을 이용할 때 호스트 운영체제로 오픈 EXPOSE 3000 CMD ["nginx", "-g", "daemon off;"] #4), (5) CMD / ENTRYPOINT # 컨테이너 시작 시, 실행될 명령어를 정하는 커맨드이기에 build로 이미지가 만들어지고, 그 이미지로 컨테이너를 run할 때 효력을 갖는다. # 컨테이너 시작 시 실행될 명령어이기에 한번만 사용 가능한걸로 알고 있다. 사실 두 개의 명령어는 비슷한 면이 있지만, run 시에 조금 달라진다. # 자세한 건.. 저도 잘 모릅니다. # 여기서는 from을 두 개 사용했는데 from 두개 사용하는 것을 멀치 스테이지라고 부릅니다.
Backend dockerfile(최종 - 설명은 frontdocker파일과 비슷해서 적지 않았습니다.)
FROM openjdk:11 AS builder WORKDIR /backend COPY . . RUN chmod +x ./gradlew RUN rm -rf module-api/src/test RUN rm -rf module-web/src/test RUN ./gradlew :module-web:clean build RUN ls module-web/build/libs FROM adoptopenjdk:11-jdk COPY --from=builder /backend/module-web/build/libs/*.jar app.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "./app.jar"]
주의사항
env.local이나 백엔드 프론트 환경파일은 관리자가 직접 서버에 배포해야합니다.(레포지토리 주소 : home 디렉터리 docker폴더 안에 있습니다)
충돌이 일어날 경우 서버에 접속해서 직접 도커를 중단 제거 삭제를 해주셔야합니다.(위쪽에 명령어 정리해놨습니다.)
'👾개발지식 > DevOps' 카테고리의 다른 글
letsencrypt사용해서 ssl인증서 달기 (0) | 2021.11.08 |
---|---|
EC2에서 HTTPS간 통신 구현하기 (0) | 2021.11.08 |
도커 기본 명령어 (0) | 2021.11.08 |
우분투 20.04 환경에서 도커(Docker) 설치 및 삭제 방법 (0) | 2021.09.09 |
도커를 사용해서 MYSQL설치하고 접속하기 (0) | 2021.07.28 |
댓글