배포 : EC2, Docker, Jenkins로 CI/CD 파이프라인 구축하기

cad·2023년 9월 7일
0

SSAFY

목록 보기
3/5
post-thumbnail

개요

SSAFY 2학기 첫 프로젝트를 하면서 배포 파트를 내가 맡게 되었다. 백엔드에서 API를 개발하고 로컬에서만 배포하고 테스트해봤기 때문에 실제로 인터넷에서 내가 만든 웹 사이트의 결과물을 보고 싶었다. 그래서 꼭 배포를 해보고자 생각했다.

SpringBoot 서버를 배포했어야 했는데 Docker랑 리눅스에 대해선 어느정도 이전에 사용해봤기 때문에 수동으로 배포하는건 사실 어렵지 않았다. 도커를 사용하지 않을거면 EC2에 java를 설치하고 빌드한 jar 파일을 리눅스 scp 명령어로 파일을 옮겨 실행시키면 끝이고 Docker 를 사용할 것이면 Dockerfile을 작성해서 이미지를 생성하고 Container를 생성하면 된다.

여기까지가 기본적인 수동 배포 방법인데 우리 팀에서는 이렇게 배포하는 사람을 인간 젠킨스라고 불렀다. 처음에는 백엔드 개발할 것도 바쁘고 해서 인간 젠킨스로 활동하다가 점차 배포할 일이 많아져서 빨리 자동 배포를 구축하고자 했다. 사진이 많아서 글이 매우 길다.

시스템 아키텍처

우리 팀의 최종 배포 아키텍처이다. 뭔가 오브젝트가 많아서 복잡해보이지만 크게 단계를 나누자면 아래와 같다.

  1. Gitlab에서 배포 branch 에 Push 진행
  2. EC2의 Jenkins에서 Gitlab Push를 감지
  3. Dockerfile 빌드 후 컨테이너 배포

배포 과정

1. EC2 접속

  • SSH 접속
  1. 최초 접속 시 권한 요구하면 ‘yes’ 입력
# sudo ssh -i [pem키 위치] [접속 계정]@[접속할 도메인]
$ sudo ssh -i I8A601T.pem ubuntu@i8a601.p.ssafy.io
  1. EC2 편리하게 접속하는 법
    • EC2 정보가 담긴 config파일을 만들어 번거롭게 pem와 도메인 경로를 쓰지 않고 접속할 수 있다.
      • ssh 전용 폴더 생성

        mkdir ~/.ssh 
        cd ~/.ssh // ssh 폴더 생성 및 이동
        cp [로컬 pem 키 위치] ~/.ssh // pem 키 옮기기
        vi config  // config 파일 생성
      • config 내용 추가

        Host ssafy
                HostName [서버 ip 주소]
                User ubuntu
                IdentityFile ~/.ssh/[pem키 파일 명].pem
      • ssafy 계정에 접속

        ssh ssafy

2. EC2 초기 설정

sudo apt update
sudo apt upgrade -y
sudo apt install -y build-essential

3. 한국으로 시간 설정

sudo ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime

# 시간 확인
date

EC2 환경 설정

1. Docker 설치

  1. 기본 설정, 사전 설치
sudo apt -y update
sudo apt install -y apt-transport-https ca-certificates curl software-properties-common
  1. 자동 설치 스크립트 활용
  • 리눅스 배포판 종류를 자동으로 인식하여 Docker 패키지를 설치해주는 스크립트를 제공
sudo wget -qO- https://get.docker.com/ | sh
  1. Docker 서비스 실행하기 및 부팅 시 자동 실행 설정
sudo systemctl start docker
sudo systemctl enable docker
  1. Docker 그룹에 현재 계정 추가
sudo systemctl enable docker
sudo systemctl restart docker
  • sudo를 사용하지 않고 docker를 사용할 수 있다.
  • docker 그룹은 root 권한과 동일하므로 꼭 필요한 계정만 포함
  • 현재 계정에서 로그아웃한 뒤 다시 로그인
  1. Docker 설치 확인
docker -v

Docker Compose 설치

  • 최신 버전을 가져오기 위한 jq 라이브러리 설치
$ sudo apt install jq
  • docker-compose 최신 버전 설치
$ VERSION=$(curl --silent https://api.github.com/repos/docker/compose/releases/latest | jq .name -r)
$ DESTINATION=/usr/bin/docker-compose
$ sudo curl -L https://github.com/docker/compose/releases/download/${VERSION}/docker-compose-$(uname -s)-$(uname -m) -o $DESTINATION
$ sudo chmod 755 $DESTINATION

// 터미널 재접속 하기!

$ docker-compose -v
Docker Compose version v2.x.x

* 프리티어 이용 시 RAM 1GB → 2GB로 늘리기

$ sudo dd if=/dev/zero of=/swapfile bs=128M count=16
$ sudo chmod 600 /swapfile
$ sudo mkswap /swapfile
$ sudo swapon /swapfile
$ sudo swapon -s

$ sudo vi /etc/fstab
/swapfile swap swap defaults 0 0 // 제일 아랫줄에 추가 ! (이 주석은 빼고)

2. Docker Maria DB

  1. Docker Maria DB 이미지 다운로드 받기
$ docker pull mariadb
  1. Docker에 Maria DB 컨테이너 만들고 실행하기
$ docker run --name mariadb -d -p 3306:3306 -v /var/lib/mysql_main:/var/lib/mysql --restart=always -e MYSQL_ROOT_PASSWORD=root mariadb
  • 옵션 설명
    • -v : 마운트 설정, hotst 의 /var/lib/mysql 과 mariadb의 /var/lib/mysql의 파일들을 동기화
    • -name: 만들어서 사용할 컨테이너의 이름을 정의
    • d: 컨테이너를 백그라운드에서 실행
    • p: 호스트와 컨테이너 간의 포트를 연결 (host-port:container-port) // 호스트에서 3306 포트 연결 시 컨테이너 3306 포트로 포워딩
    • -restart=always: 도커가 실행되는 경우 항상 컨테이너를 실행
    • e: 기타 환경설정(Enviorment)
    • MYSQL_ROOT_PASSWORD=root // mariadb의 root 사용자 초기 비밀번호를 설정
    • mariadb: 컨테이너를 만들 때 사용할 이미지 이름
  1. Maria DB에 database를 추가하고 user 권한 설정
  • Docker - Mariadb 컨테이너 접속하기
docker exec -it mariadb /bin/bash
  • Mariadb - 루트 계정으로 데이터베이스 접속하기
mysql -u root -p

비밀번호는 "root"

Mariadb 사용자 추가하기

예시) create user 'user_name'@'XXX.XXX.XXX.XXX' identified by 'user_password';

create user 'ssafy601'@'%' identified by 'ssafy601';

Mariadb - 사용자 권한 부여하기

예시) grant all privileges on db_name.* to 'user_name'@'XXX.XXX.XXX.XXX';
flush privileges;

grant all privileges on *.* to 'ssafy601'@'%';
flush privileges;

Mariadb - 데이터 베이스 만들기

예시) create database [db_name];

create database ssafy601;

3. Docker Redis

  • Redis 이미지 받기
docker pull redis:alpine
  • 도커 네트워크 생성 [디폴트값]
docker network create redis-network
  • 도커 네트워크 상세정보 확인
docker inspect redis-network
  • local-redis라는 이름으로 로컬-docker 간 6379 포트 개방
docker run --name local-redis -p 6379:6379 --network redis-network -v /redis_temp:/data -d redis:alpine redis-server --appendonly yes
  • Docker 컨테이너 확인
docker ps -a
  • 컨테이너 진입
# 실행 중인 redis 컨테이너에 대해 docker redis-cli 로 직접 진입
docker run -it --network redis-network --rm redis:alpine redis-cli -h local-redis

# bash로도 진입 가능하다.
docker run -it --network redis-network --rm redis:alpine bash
redis-cli
  • 권한 추가
# slaveof no one : 현재 슬레이브(복제)인 자신을 마스터로 만듭니다.
127.0.0.1:6379> slaveof no one
  • 테스트
    • OK 가 뜨면 성공

4. Dockerfile로 Jenkins images 받기 (Docker in Docker, DinD 방식)

  • Dockerfile 작성
# 폴더 생성
RUN mkdir config && cd config

# 아래 내용 작성
$ vi Dockerfile

FROM jenkins/jenkins:jdk17

#도커를 실행하기 위한 root 계정으로 전환
USER root

#도커 설치
COPY docker_install.sh /docker_install.sh
RUN chmod +x /docker_install.sh
RUN /docker_install.sh

#설치 후 도커그룹의 jenkins 계정 생성 후 해당 계정으로 변경
RUN groupadd -f docker
RUN usermod -aG docker jenkins
USER jenkins
  • docker 설치 shell 파일
#!/bin/sh
apt-get update && \
apt-get -y install apt-transport-https \
  ca-certificates \
  curl \
  gnupg2 \
  zip \
  unzip \
  software-properties-common && \
curl -fsSL https://download.docker.com/linux/$(. /etc/os-release; echo "$ID")/gpg > /tmp/dkey; apt-key add /tmp/dkey && \
add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/$(. /etc/os-release; echo "$ID") \
$(lsb_release -cs) \
stable" && \
apt-get update && \
apt-get -y install docker-ce
  • Docker 이미지 생성
docker build -t jenkins/myjenkins .
  • Docker 볼륨 폴더 권한 설정
$ mkdir /var/jenkinsDir/
$ sudo chown 1000 /var/jenkinsDir/
  • Jenkins 컨테이너 생성
docker run -d -p 9090:8080 --name=jenkinscicd \
-e TZ=Asia/Seoul
-v /var/jenkinsDir:/var/jenkins_home \
-v /var/run/docker.sock:/var/run/docker.sock \
jenkins/myjenkins
  • 옵션 설명 -d : 는 백그라운드에서 실행을 의미 -p 는 매핑할 포트를 의미합니다. ( p가 port의 단축어가 아니었음 .. ) : 기준으로 왼쪽은 로컬포트, 오른쪽은 도커 이미지의 포트를 의미합니다. 도커 이미지에서의 8080 포트를 로컬 포트 9090으로 매핑한다는 뜻입니다.
    -v /var/run/docker.sock:/var/run/docker.sock \
    jenkins/myjenkins
    이 옵션은 로컬의 도커와 젠킨스 내에서 사용할 도커 엔진을 동일한 것으로 사용하겠다는 의미입니다. v 옵션은 ":"를 기준으로 왼쪽의 로컬 경로를 오른쪽의 컨테이너 경로로 마운트 해줍니다. 즉, 제 컴퓨터의 사용자경로/jenkinsDir 을 컨테이너의 /var/jenkins_home과 바인드 시켜준다는 것입니다. 물론, 양방향으로 연결됩니다. 컨테이너가 종료되거나 알 수없는 오류로 정지되어도, jenkins_home에 남아있는 소중한 설정 파일들은 로컬 경로에 남아있게 됩니다.

Jenkins 초기 세팅 및 테스트

  • 젠킨스에 접속하기 전에 /var/run/docker.sock 에 대한 권한을 설정해주어야 합니다.
  • 초기 /var/run/docker.sock의 권한이 소유자와 그룹 모두 root였기 때문에 이제 그룹을 root에서 docker로 변경해줄겁니다.
  • 먼저, jenkins로 실행됐던 컨테이너의 bash를 root 계정으로 로그인 하기전에, 현재 실행되고 있는 컨테이너의 정보들을 확인할 수 있는 명령어를 입력해 아이디를 확인하겠습니다.
docker ps -a

  • 우리가 방금 생성한 컨테이너의 ID는 0bcdb8~ 입니다. 도커는 다른 컨테이너 ID와 겹치지 않는 부분까지 입력하면 해당 컨테이너로 알아서 매핑해줍니다.
docker exec -it -u root 컨테이너ID /bin/bash

exec는 컨테이너에 명령어를 실행시키는 명령어인데, /bin/bash와 옵션 -it를 줌으로써 컨테이너의 쉘에 접속할 수 있습니다.

이제 정말로 root 계정으로 컨테이너에 접속하기 위해 컨테이너ID에 0bc를 입력해 실행합니다.

  • root 계정으로 로그인이 잘 되었습니다. 이제 그룹을 바꾸기 위해 다음 명령어를 실행해줍니다.
sudo chown root:docker /var/run/docker.sock
  • 그리고 이제 쉘을 exit 명령어로 빠져나온 후 다음 명령어를 실행해 컨테이너를 재실행해줍니다.
docker restart [컨테이너 ID]
  • Jenkins 패스워드 확인
docker logs [jenkins 컨테이너 ID]
  • docker logs 컨테이너 id를 입력해 로그를 출력하면 initialAdminPassword가 출력됩니다. 이 패스워드를 입력해주면 됩니다.

  • 보안 그룹 설정을 해야 {public ip}:9090 에 접근할 수 있습니다.

인바운드 규칙 편집 클릭

규칙 추가 클릭 → 유형: TCP, 포트 범위: {등록할 포트 번호}


젠킨스 설정

  • 정상적으로 입력했다면 플러그인 설치가 나오는데, 우리는 Install suggested plugins를 선택합니다.

  • 설치가 완료되면, 어드민 계정 생성창이 나오고, 본인이 사용하실 정보들을 입력해줍시다.

  • 앞으로 이 url로 젠킨스에 접속하시면 됩니다.

Jenkins 플러그인 설정

  • Gitlab, Docker 플러그인을 받습니다.

Gitlab

Docker

  • 여기까지 오셨다면, 젠킨스 설치 및 초기 세팅 완료!

CI/CD (빌드 및 배포) 세팅

  • 먼저, 대쉬보드의 새로운 아이템을 클릭합니다.

  • 아이템이름을 자유롭게 입력해주시고, Freestyle project를 선택하고, OK로 생성합니다.
  • 이제 빌드 설정창이 뜰텐데, 소스 코드 관리쪽에서 Git을 선택하고, Repository URL에 다음과 같이 입력해줍니다.
  • Gitlab 저장소를 입력하시면 됩니다.

  • Credentials 에서 Userame / password로 선택하고 SSAFY Email 로 두가지 모두 입력하고 계정 인증을 진행합니다.
  • 이제 Build에서 Execute shell을 선택해줍니다.

  • 폴더 내에 prod와 dev 두가지 버전이 존재하며 prod 환경 기준으로 설명하겠습니다.
  • Gitlab의 저장소와 연동되어 폴더 내의 start-prod.sh 파일이 실행 됩니다.
bash start-prod.sh

빌드 확인

  • 이제 드디어 세팅한 값들을 확인해볼 차례입니다. 위의 내용들을 저장하고 Build Now를 눌러봅니다.

Webhook 설정

  • Jenkins 트리거 체크

  • Jenkins 에서 빌드 유발Build when …고급 → 하단에 Secret token Generate → 토큰 발급 완료!
  • Gitlab Webhook에서 해당 토큰을 등록합니다.
    1. lab.ssafy.com Gitlab 프로젝트 접속
    2. 좌측 Settings → Webhook 접속
    3. 아래와 같이 URL 란에 Jenkins에서의 Item URL를 입력하고 Save를 눌러줍니다.

배포 shell 파일

docker-compose -f docker-compose-prod.yml pull

COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker-compose -f docker-compose-prod.yml up --build -d

docker rmi -f $(docker images -f "dangling=true" -q) || true

docker-compose-prod.yml

version: "3"
services:
  server:
    container_name: server
    build:
      context: ./SHabit-back
      args:
        SERVER_MODE: prod
    ports:
      - 8080:8080
    environment:
      - TZ=Asia/Seoul
  client:
    container_name: client
    build:
      context: ./shabit-front
      dockerfile: Dockerfile.dev
    ports:
      - 3000:3000
    depends_on:
      - server
  nginx:
    container_name: nginx
    build: ./.nginx
    depends_on:
      - server
      - client
    volumes:
      - .nginx/conf.d:/etc/nginx/conf.d
      - .nginx/zerossl:/var/www/zerossl/.well-known/pki-validation
      - .nginx/cert:/cert
    ports:
      - 80:80
      - 443:443

빌드 절차

  • Gitalb에서 Jenkins로 Webhooks을 연동한 다음 해당 브랜치에 push, merge 를 진행합니다.
    • start-prod.sh 쉘 파일 실행
    • docker-compose 실행
    • Dockerfile 실행
    • Server → Front 순으로 배포 진행

배포시 주의 사항

  1. 프론트 엔드에서 서버 주소는 shabit.site 이므로 로컬 환경에서 구동할 시 localhost 로 변경해야합니다.
  2. 백엔드는 prod, dev, local 세 가지 파일로 분리하여 ip 주소는 건들이지 않고 db 주소 및 계정 정보를 변경하면 됩니다.

Github 주소

https://github.com/wlwlsus/shabit

Ref

https://velog.io/@hind_sight/Docker-Jenkins-도커와-젠킨스를-활용한-Spring-Boot-CICD

profile
Dare mighty things!

0개의 댓글

관련 채용 정보