진행 상황
백엔드와 프론트 서버가 배포되어 있는 상태입니다. 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 |
댓글