[서버 배포] Docker & Jenkins CI/CD

손효재·2022년 9월 30일
1

DevOps

목록 보기
1/1
post-thumbnail

Intro

프로젝트에서 Docker와 Jenkins를 활용하여 CI/CD 환경을 구축하였으며, 설정했던 서버배포 과정을 정리하려 합니다.

🛫 서버접속 및 설정

* AWS EC2 인스턴스 생성 이후 보안 그룹 - 인바운드 규칙 에서
SSH 접속을 위해 22번 포트에 모든 IP를 허용할 수 있도록 0.0.0.0/0 으로 설정

pem키 디렉토리로 이동 → 서버 접속

$ ssh -i [pem키이름].pem ubuntu@[도메인명]
Untitled

❗️ 권한에러가 발생하여 권한을 변경해주었다.

$ chmod 400 [pem키이름].pem
Untitled (1)

방화벽 설정

서버내에서 방화벽 설정

// 방화벽상태 확인
$ sudo ufw status

// 22번포트 open
$ sudo ufw allow 22

$ sudo ufw enable
Untitled (2)

ufw 설정시에 22번 포트가 막히게 되면 콘솔 접속이 불가능해지기때문에 항상 ALLOW가 되어 있어야 한다.

🐳 도커 설치

오래된 버전의 도커 삭제

$ sudo apt-get remove docker docker-engine docker.io containerd run

Docker CE를 설치하기 전에 먼저 도커 저장소(repository)를 설정해야 한다.
이후에 저장소로부터 도커를 설치하거나 업데이트할 수 있다.

도커 저장소 설정

$ sudo apt-get update

// 의존성 패키지 설치
$ sudo apt-get install apt-transport-https ca-certificates curl gnupg-agent software-properties-common

// Docker 패키지 인증 키 추가
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

// 인증키 확인 (아래그림)
$ sudo apt-key fingerprint 0EBFCD88

// Docker 저장소 추가
$ sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"

// 저장소 업데이트
$ sudo apt-get update

인증키 확인

fingerprint 9DC8 5822 9FC7 DD38 854A E2D8 8D81 803C 0EBF CD88에서 마지막 8자를 검색하여 fingerprint 인식 키가 있는지 확인한다.

Untitled (3)

도커 CE 최신버전 설치

$ sudo apt-get install docker-ce docker-ce-cli containerd.io

// 도커 버전 확인
$ sudo docker --version
Untitled (4)

도커 버전이 확인되며 도커가 잘 설치된것을 확인할 수 있다.

🎄 mysql

참고 : https://velog.io/@_nine/Docker-MySQL설치-및-접속하기

다음으로 도커 컨테이너에 mysql을 올려보자

// 도커 mysql 이미지 다운
$ sudo docker pull mysql

// 도커 이미지 확인
$ sudo docker images

// 3306 포트열기
$ sudo ufw allow 3306

// 컨테이너 mysql 적재
$ sudo docker run -d --name mysql -e MYSQL_ROOT_PASSWORD=[패스워드] -p 3306:3306 mysql

// 컨테이너 확인
$ sudo docker ps

받아온 mysql 이미지 확인

Untitled (5)

도커에 올라간 mysql 컨테이너 확인

Untitled (6)

Mysql 관리자 접속 확인

// 컨테이너에 접속
$ sudo docker exec -it mysql bash

// 관리자로 접속
$ mysql -u root -p

$ 패스워드 입력

Untitled (7)

database 리스트 확인

Untitled (8)

사용자 추가

use mysql

CREATE USER '사용자명'@'%' IDENTIFIED BY '비밀번호';

GRANT ALL PRIVILEGES ON *.* TO '사용자명';

FLUSH PRIVILEGES;

Untitled (9)

Untitled (10)

DB 연결

스크린샷 2022-09-16 오후 12 12 24
  • connection name : 원하는 이름으로 지정
  • Hostname : 부여받은 도메인
  • Username : 위에서 등록한 유저이름
  • Store in keychain 클릭해서 위의 패스워드 입력
    이후 아래의 Test Connection 클릭해서 다시 패스워드 입력 후 Ok 눌렀을때 Success 나오면 연동 성공!

여기까지 AWS EC2서버에 Docker 설치 및 설정 & Mysql 컨테이너를 띄워봤다.
다음은 Jenkins 설치 및 설정 & Gitlab 웹훅을 이용한 Jenkins 연동이다.

👴🏻 Jenkins 설치

Jenkins 설치 & 설정

// jnekins image pull
$ sudo docker pull jenkins/jenkins:lts

// image 확인 (아래 사진)
$ sudo docker images

//8080 포트 열기
$ sudo ufw allow 8080

// 이미지를 컨테이너에 적재 (2번째 명령어로 실행하자)
// !!처음시도한 젠킨스 컨테이너인데 docker in docker 이슈가 있었음 (젠킨스 설정쪽에 다시 설명)
// $ sudo docker run --name jenkins -d -p 8080:8080 -p 50000:50000 -v /home/jenkins:/var/jenkins_home -u root jenkins/jenkins:lts

// docker in docker를 해결하기 위해 컨테이너를 삭제하고 다시 시도했음
$ sudo docker run --name jenkins -d -p 8080:8080 -p 50000:50000 -v /home/jenkins:/var/jenkins_home -v /var/run/docker.sock:/var/run/docker.sock -e TZ=Asia/Seoul -u root jenkins/jenkins:lts

// 도커 컨테이너 확인 (아래 사진)
$ sudo docker ps

// 초기 비밀번호 확인 (아래 사진)
$ sudo docker logs jenkins

// 도메인:8080으로 젠킨스 접근

젠킨스 이미지 확인

도커 컨테이너에 젠킨스 적재 확인

초기 비밀번호 확인

젠킨스 접근

  1. 도메인명:8080으로 젠킨스 접근
  2. 초기 비밀번호 입력하여 로그인
  3. 플러그인 설치 (Install suggested plugins로 플러그인 자동설치)
  4. 사용자 계정설정
  5. 젠킨스 url 설정

젠킨스 로그인 성공

젠킨스 플러그인 추가 설치

좌측의 Dashboard에서 Jenkins관리 → 플러그인 관리
→ 설치가능에서 gitlab과 docker를 검색하여 다음 플러그인들을 설치한다. ( install without restart )

🌈 젠킨스 - 깃랩 연동

깃랩 액세스 토큰 생성

Gitlab의 Settings → Access Tokens에서 토큰명, 만료기간, scope 설정을 하고 create로 토큰 생성
* 한번만 보여주니 복사해놓자

젠킨스 프로젝트 생성

메인화면 좌측 New Item → Freestyle project 클릭하여 backend, frontend 이름으로 2개의 item 생성

* 이하 item project 설정은 backend, frontend 따로 해준다.

젠킨스 토큰생성

생성한 프로젝트 → 구성 → Build Trigger(빌드 유발) 항목에서 Build when a change is pushed to GitLab 선택!

고급 클릭 → Secret Token의 Generate 클릭해서 토큰 생성

각 프로젝트마다 위 사진의 url과 생성한 token을 각각 메모해둔다.

깃랩 웹훅 만들기

Gitlab 프로젝트의 setting → webhook 클릭

  • URL: 젠킨스 토큰생성할때 Build when a change is pushed to GitLab 옆에있는 url 입력
  • Secret token : 젠킨스에서 생성한 secret token 입력
  • Push events: 적용할 브랜치입력

입력후 아래의 Add webhook을 클릭해서 웹훅을 생성해준다.

생성하면 아래 Add webhook 버튼 밑에 등록된 웹훅이 표시된다.

웹훅 테스트

Test 클릭해서 push events로 웹훅 테스트

성공시 아래 사진처럼 확인가능

젠킨스 연동

프로젝트 선택 → 구성 → 소스코드 관리 → Git

Repository URL : https://”깃랩토큰명”:”깃랩액세스토큰”@”gitlab url”

Credentials는 Add를 클릭해서 username with password로 깃랩 아이디와 비밀번호를 입력해서 credential 생성

젠킨스에서 Credential 추가

Jenkins관리 → Manage Credentials설정 → Jenkins → Global → Add Credentials

깃랩 액세스 토큰을 입력하여 Credential을 생성한다.

API token : 깃랩 액세스 토큰
ID : 임의의 ID

backend, frontend 총 2개의 프로젝트를 생성했고, 깃랩에서 웹훅 테스트를 진행했을때 아래그림처럼 build되는것을 확인할 수 있다.

🍊 젠킨스 shell script에서 docker 에러

❗️ 쉘 스크립트에서 docker를 실행할 수 없는 에러

해결방법 : Docker in Docker 설정이 필요하다

방법1. 젠킨스 내부에 도커 설치

// 젠킨스 컨테이너 접속
docker exec -it -u root jenkins bash

// 젠킨스내에서 도커 설치
curl https://get.docker.com/ > dockerinstall && chmod 777 dockerinstall && ./dockerinstall

방법2. Jenkins가 설치되어있는 서버의 docker 볼륨과 Jenkins 컨테이너 안 docker의 볼륨을 연결해 사용(jenkins를 docker 컨테이너에 올릴때)

https://devbksheen.tistory.com/entry/Jenkins를-이용한-Docker-컨테이너-자동-배포하기Blue-Ocean-NCP

https://jijs.tistory.com/entry/docker로-젠킨스-설치

// 젠킨스 실행할때 옵션추가 (-v /var/run/docker.sock:/var/run/docker.sock)
// docker run -d -it -v /var/run/docker.sock:/var/run/docker.sock -p 8080:8080 --name jenkins jenkins/jenkins:lts

// Jenkins 컨테이너에 접속
$ docker exec -it -u root jenkins bash

// jenkins계정에 Docker 볼륨을 사용할 수 있는 권한부여
$ chown jenkins:jenkins /var/run/docker.sock

❗️ 1번 방법으로 젠킨스 내부에 도커를 설치해주었는데 다른 에러가 발생한다.

위와 같은 명령어가 뜨면 docker service가 실행이 안되어있는것이다.

참고 : https://velog.io/@pop8682/Docker-Cannot-connect-to-the-Docker-daemon-at-unixvarrundocker.sock.-Is-the-docker-daemon-running-에러-해결

! 해결되야하지만, 나는 여기서다시 에러가 발생한다.
// docker daemon 상태 확인
$ sudo systemctl status docker

// docker를 daemon으로 실행
$sudo systemctl start docker

// 새로 시작하거나 부팅시 자동으로 docker daemon을 실행
$sudo systemctl enable docker

아래와 같은 에러 발생

진짜해결!!

방법1과 2를 모두 사용했다.

먼저 기존의 젠킨스 컨테이너를 삭제하고, docker in docker가 가능하게 젠킨스를 실행했다. (방법2)

// 젠킨스 컨테이너 정지
$ sudo docker stop jenkins

// 젠킨스 컨테이너 삭제
$ sudo docker rm jenkins

// docker in docker를 추가하여 젠킨스 컨테이너 실행
$ sudo docker run --name jenkins -d -p 8080:8080 -p 50000:50000 -v /home/jenkins:/var/jenkins_home -v /var/run/docker.sock:/var/run/docker.sock -e TZ=Asia/Seoul -u root jenkins/jenkins:lts

// Jenkins 컨테이너에 접속
$ docker exec -it -u root jenkins bash

// jenkins계정에 Docker 볼륨을 사용할 수 있는 권한부여
$ chown jenkins:jenkins /var/run/docker.sock

방법2로 진행해도 docker가 실행되지 않았다.

참고 : https://devbksheen.tistory.com/entry/Jenkins를-이용한-Docker-컨테이너-자동-배포하기Blue-Ocean-NCP

이 상태에서 방법1의 도커설치까지 진행해주었다.

// (젠킨스 컨테이너 내부에서) 젠킨스내에서 도커 설치
$ curl https://get.docker.com/ > dockerinstall && chmod 777 dockerinstall && ./dockerinstall

젠킨스 컨테이너 내부에서 docker 명령어가 잘 실행된다!!

이제 젠킨스에서 쉘 스크립트로 프로젝트의 도커파일을 실행시켜서 도커 컨테이너에 띄워보자

🍎 젠킨스 쉘 스크립트 & Docker 파일 작성

backend 쉘 스크립트

cd backend
docker build -t backend .
docker ps -q --filter "name=backend" | grep -q . && docker stop backend && docker rm backend | true
docker run -p 8081:8080 -d -e TZ=Asia/Seoul --name=backend backend
docker rmi -f $(docker images -f "dangling=true" -q) || true

backend 도커파일

FROM openjdk:8-jdk-slim as builder

COPY gradlew .
COPY gradle gradle
COPY build.gradle .
COPY settings.gradle .
COPY src src
RUN chmod +x ./gradlew
RUN ./gradlew bootJar

FROM openjdk:8-jdk-slim
COPY --from=builder build/libs/*.jar app.jar
ENTRYPOINT ["java","-jar","-Dspring.profiles.active=gcp","/app.jar"]
EXPOSE 8081

❗️도커에서 backend 컨테이너가 실행되자마자 exit 되는 에러가 발생했다.

docker ps -a 로 전체 컨테이너를 확인하니 exit 상태이고, start 명령어로 컨테이너를 실행해도 바로 exit된다

$ docker logs backend 명령어로 로그확인 결과 환경변수 설정파일 env.properties 가 없어서 생긴 에러

젠킨스 컨테이너 내부에 env.properties를 생성하여 해결한다.

// 젠킨스 컨테이너 내부 접속
$ docker exec -it -u root jenkins bash

// workspace의 backend로 이동
$ cd /var/jenkins_home/workspace/backend/backend/src/main

// properties폴더와 env.properties만들어서 설정추가
$ cd resources
$ mkdir properties
$ cd properties
$ cat > env.properties
환경변수 설정파일(env.properties) 복붙
ctrl+c로 저장후 종료

// 저장된 내용 확인
$ cat env.properties

환경변수파일을 젠킨스 컨테이너안에 넣었는데 계속 동일한 에러가 발생한다. → 컨테이너를 삭제하고 다시 빌드했다

// backend 컨테이너 삭제
$ docker rm backend

backend 컨테이너 성공!

삭제이후 젠킨스에서 다시 빌드해주었고 이상없이 도커에 backend 컨테이너가 올라갔다

frontend 쉘 스크립트

// ver1 쉘 스크립트로 nginx 설정
cd frontend
docker build -t frontend .
docker ps -q --filter "name=frontend" | grep -q . && docker stop frontend && docker rm frontend | true
docker run -d -p 80:80 -p 443:443 -v /home/ubuntu/certbot/conf:/etc/letsencrypt/ -v /home/ubuntu/certbot/www:/var/www/certbot -v /home/ubuntu/nginx/conf/default.conf:/etc/nginx/conf.d/default.conf --name frontend frontend
docker rmi -f $(docker images -f "dangling=true" -q) || true
// ver2 도커파일로 nginx 설정
cd frontend
docker build -t frontend .
docker ps -q --filter "name=frontend" | grep -q . && docker stop frontend && docker rm frontend | true
docker run -d -p 80:80 -p 443:443 -v /home/ubuntu/certbot/conf:/etc/letsencrypt/ -v /home/ubuntu/certbot/www:/var/www/certbot --name frontend frontend
docker rmi -f $(docker images -f "dangling=true" -q) || true

frontend 도커파일

# build stage
FROM node:lts-alpine as build-stage
WORKDIR /app
COPY package*.json ./
RUN yarn install
COPY . .
RUN npm run build

# production stage
FROM nginx:stable-alpine as production-stage
~~COPY ./default.conf /etc/nginx/conf.d~~
COPY --from=build-stage /app/build /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

❗️도커에서 frontend 컨테이너가 실행되자마자 exit 되는 에러가 발생했다.

docker ps -a 로 전체 컨테이너를 확인하니 exit 상태이고, start 명령어로 컨테이너를 실행해도 바로 exit된다.

$ docker logs frontend 로 로그확인

아래와 같은 에러 발생

nginx: [emerg] "server" directive is not allowed here in /etc/nginx/nginx.conf:1 에러

→ dockerfile에서 nginx설정파일이 제대로 COPY되지 않은것으로 예상된다
(이때는 도커파일에 경로가 잘못되었고, COPY ./default.conf /etc/nginx/conf.d 로 수정해야했다)

여기서 nginx설정파일을 도커파일이 아닌 쉘 스크립트에서 docker volume으로 설정파일을 지정하는 방법으로 바꿔봤다. (쉘 스크립트 ver1)

volume으로 지정해줄 경로상에 nginx설정파일을 저장한다.

// 경로상의 폴더로 이동
$ cd nginx/conf

// nginx 설정파일 생성 및 저장
$ sudo vi default.conf

🍉 nginx 설정파일

server {
    listen 80;
    server_name [도메인명];
    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl;
    server_name [도메인명];
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    ssl_certificate /etc/letsencrypt/live/[도메인명]/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/[도메인명]/privkey.pem;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2 SSLv3;
    ssl_ciphers ALL;

    location / {
        root /usr/share/nginx/html;
        index index.html index.htm
        proxy_redirect off;
        charset utf-8;
        try_files $uri $uri/ /index.html;

        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        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-Nginx-Proxy true;
    }

}

💡 Jenkins 쉘 스크립트 & Docker File 설명

Jenkins에서 사용한 쉘 스크립트와 작성한 DockerFile을 자세히 알아보자

backend 도커파일

FROM openjdk:8-jdk-slim as builder

COPY gradlew .
COPY gradle gradle
COPY build.gradle .
COPY settings.gradle .
COPY src src
RUN chmod +x ./gradlew
RUN ./gradlew bootJar

FROM openjdk:8-jdk-slim
COPY --from=builder build/libs/*.jar app.jar
ENTRYPOINT ["java","-jar","-Dspring.profiles.active=gcp","/app.jar"]
EXPOSE 8081

frontend 도커파일

  1. Build Stage
# build-stage
FROM node:lts-alpine as build-stage
# 기본 이미지는 node:lts-alpine 이며, build-stage라는 별칭을 가진다.

WORKDIR /app
# 작업디렉토리를 /app 으로 이동한다

COPY package*.json ./
# package.json 파일을 작업 디렉토리로 복사한다. npm의 모든 필수 종속성 설치를 위해 해당 파일이 필요

RUN yarn install
# yarn install 명령어로 종속성들을 설치한다.

COPY . .
# 나머지 파일들을 작업 디렉토리에 복사한다. 실제 어플리케이션 코드가 포함되어 있다.

RUN npm run build
# npm run build 명령어로 빌드파일을 준비한다
  1. Production Stage
# production-stage
FROM nginx:stable-alpine as production-stage
# stable-alpine 버전의 nginx 기본 베이스로 설정

COPY ./default.conf /etc/nginx/nginx.conf
# 로컬에서 만든 default.conf(nginx 설정)을 nginx의 서버에 복사

COPY --from=build-stage /app/build /usr/share/nginx/html
# nginx가 동작하기위해 필요한 파일들을 시스템으로 복사한다
# --from=builder-stage 태그를 이용하여 위의 빌드 단계의 이미지의 작업 디렉토리(/app/build)에서 nginx의 디렉토리로 파일을 복사한다.

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

🤖 certbot

cetbot container 생성 및 인증서 발급

// 443 포트열기
$ sudo ufw allow 443

// /home/ubuntu에 certbot 디렉토리 생성
$ cd
$ sudo mkdir certbot

// certbot 디렉토리에서 conf, www 디렉토리 생성
$ cd certbot
$ sudo mkdir conf www logs

// 80번포트 서버 정지(encrypt 인증방식인 standalone을 위해)
$ sudo service nginx stop

$ sudo docker pull certbot/certbot
$ sudo docker run -it --rm --name certbot -p 80:80 -v "/home/ubuntu/certbot/conf:/etc/letsencrypt" -v "/home/ubuntu/certbot/log:/var/log/letsencrypt" -v "/home/ubuntu/certbot/www:/var/www/certbot" certbot/certbot certonly

certbot 컨테이너에서 만든 인증서를 외부에서 사용하기 위해 생성한 디렉토리를 certbot 컨테이너와 연동

* Let's Encrypt 의 인증 방식인 Standalone plugin 은 서버 인증을 위해서 80 포트를 이용한다.
따라서 nginx, apache 와 같이 80 포트를 이용하는 서비스가 있다면 서버를 일시적으로 정지시켜줘야 한다.

SSL 인증서 발급

standalone방식,<이메일>, agree, no, <domain_name>으로 작성하여 인증서를 발급한다.

인증서 발급 성공시 2개의 pem파일 생성완료! (아래는 위의 ssl인증서 발급사진에서 성공부분 확대사진)

* 젠킨스 쉘 스크립트쪽을 하다가 pem파일을 확인하려 다시 돌아왔는데 /etc 안에 letsencrypt가 없었다. 그래서 다시 nginx서버를 정지하고 인증서 발급 시도

다시 받아도 똑같이 저장되었다는 경로상에서 pem파일을 확인할 수 없다.

certbot image와 디렉토리까지 전부 삭제하고 다시 시도해보았다.

// certbot 이미지삭제
$ docker rmi [이미지id]

// 생성한 certbot 디렉토리 전체 삭제(-r, -f 옵션으로 안에 파일이 있어도 전부 삭제)
$ sudo rm -rf certbot

결과는 동일하게 관련 폴더를 찾을 수 없었고, 폴더명(letsencrypt)을 넣어서 찾아봤다.

// 폴더 찾아보기
$ find / -name 폴더명 -type d

etc/letsencrypt 경로가 아닌 /certbot/conf 내부에 존재했다. certbot 인증서를 받을때 설정한 도커 volume 으로 해당경로에 저장된 것 같다.

Spring Boot ssl 설정

.pem파일을 openssl을 이용하여 .p12(PKCS12)파일로 변환하자

참고 : https://veneas.tistory.com/entry/Spring-Boot-스프링-부트-SSL-인증서-적용-TLS-p12

// 해당 pem 파일들을 vi 에디터로 들어가서 복사 후 로컬서버로 가져온다.
$ vi [privatekey pem파일]
$ vi [certificate pem파일]
// 복사 후 로컬서버에 동일하게 파일들 생성 후 복사한 내용 입력

// [PEM -> PKCS12]
$ sudo openssl pkcs12 -inkey [privatekey] -in [certificate] -export -out [출력할파일명]
ex) sudo openssl pkcs12 -inkey privkey1.pem -in fullchain1.pem -export -out spring_key.p12

// 비밀번호 입력, 확인

변환한 p12파일을 스프링부트 프로젝트 내부에 넣어준다.

// home으로 파일 이동
$ sudo mv spring_key.p12 /home/ubuntu
$ cd

// 젠킨스 컨테이너안의 경로로 파일 복사
$ docker cp spring_key.p12 jenkins:/var/jenkins_home/workspace/backend/backend/src/main/resources

젠킨스 컨테이너 내부에서 확인결과 잘 복사된것을 확인할 수 있다.

변환해서 나온 PKCS12 파일을 스프링부트 프로젝트에 포함시킨다.

* 프로젝트 내에 포함시킬 경우 따로 경로를 지정하지 않아도되지만,
스프링부트 프로젝트 내에 포함시키지 않을경우 .p12파일 경로를 지정해야한다.

ssl 적용을 위해 application.properties 설정을 추가한다

# ssl
security.require-ssl=true
server.ssl.key-store=classpath:[p12파일명].p12
server.ssl.key-store-type=PKCS12
server.ssl.key-store-password=[ssl비밀번호]
server.ssl.enabled=true

Spring Boot 실행시 https로 시작되는것을 확인할 수 있다.

ssl비밀번호를 환경변수로 설정했기때문에, 젠킨스 컨테이너내부에 env.properties에 ssl-pw을 추가해주고, Git ignore에 p12파일을 추가해준다.

0개의 댓글