슬기로운 개발자생활/DevOps

Docker, Jenkins 활용 CI/CD 구축 (React, Kotlin+Spring) 3편 - 웹서버에 배포

개발자 소신 2024. 1. 4. 21:07
반응형

Jenkins와 Github를 연동하여 푸시이벤트가 발생했을 시, Jenkins에서 빌드를 진행하는 것까지 설정을 해놓았으니, 이제 빌드 후에 일어날 작업들을 설정해두면 자동배포 설정이 끝난다.
한 편 을 더 추가해야 할 것 같지만.. 2022년 마지막날에 끝내고 싶어서 다 합쳐버렸다. 뒤의 과정이 상당히 긺으로 천천히 따라오실 것을 추천..

순서

0.  전체 흐름
1.  publish over ssh
2.  react 빌드 후 파일 전송
3.  Docker in Jenkins
4.  spring 도커 빌드 후 도커 push
5.  docker-compose로 백엔드 배포
6.  Trouble Shotting

 

nginx 설정

0. 전체 흐름

Architecture

  • 이번 프로젝트에서 구축한 CI/CD의 아키텍처

1. publish over ssh

  • 다른 서버에 SSH 로 접속하여 파일을 전송하거나 특정 명령어를 실행하는데 사용

publish-over-ssh

  • 이전에 깃허브 플러그인을 설치했듯이, publish over ssh를 검색하여 설치해준다.

to-setting

  • 시스템 설정으로 들어가서 서버 관련 내용을 입력해준다.

ssh-setting

 

(24.06.24 수정)

24.04 버전으로 테스트해본 결과

jenkins 서버에서 `ssh-keygen -t rsa` 로 키파일을 생성하여 (cat ~/.ssh/id_rsa) 파일 내용을 Key자리에 입력하고

배포서버의 (~/.ssh/authroized_keys) 에 (cat ~/.ssh/id_rsa.pub) 파일안에 있는 내용을 추가해준다.

  • Key : ssh-keygen으로 생성한 비밀 키 (~/.ssh/id_rsa)
  • Name : 해당 서버 식별자
  • Hostname : 배포서버 public ip 주소
  • Username : ec2 유저 이름 (ubuntu)
  • Remote Directory : 원격서버 작업디렉토리 (/home/ubuntu)

여기서 [고급] 탭에 있는 use password 부분이 체크되어있다면 체크 해제하도록 하자

 

더보기

※ 일단 본인은 aws ec2를 ubuntu 22.04 이미지로 생성하였기 때문에 뒷부분이 아무리해도 접속이 되지 않았음. 뭔 짓을 해도 안된다면 버전을 18.04로 낮춰서 시도해보길 바람
(23.01.06 수정 - 18.04로 하면 바로 됨 ^^)

  • Key : ec2 생성 시 발급받은 pem파일
  • Name : 해당 서버 식별자
  • Hostname : public ip 주소
  • Username : ec2 유저 이름 (ubuntu)
  • Remote Directory : 원격서버 작업디렉토리 (/home/ubuntu)

 

네이버 클라우드 플랫폼의 경우)

  1. ssh-keygen -t rsa로 키페어 파일을 생성한다. jenkins, web서버 두 곳 모두 생성
  2. Jenkins 서버) /root/.ssh에 있는 id_rsa.pub를 배포 서버에 /root/.ssh/known_hosts에 입력한다.
  3. Hostname에 포트포워딩 비공인 ip 주소로 연결시도

2. react 빌드 후 파일 전송

  • jenkins pipeline을 사용하면 쉽게 설정이 되는 것 같은데, 여기선 Nodejs plugin을 설치하여 react 프로젝트 실행 전 nodejs 환경을 셋팅해놓고 build를 한다.

nodejs-plugin

  • NodeJS 플러그인 설치

global-tool-configuration

  • 이번엔 Jenkins 관리에서 global tool configuration으로 들어간다.

nodejs-install

  • 사용할 NodeJS의 식별자 설정, NodeJS 버전을 선택한다. global로 설치해놓을 패키지를 설정할 수 있다. (yarn 명령어을 사용하는 사람은 여기서 yarn을 입력)

node-setting

  • item 구성에서 빌드 환경 -> Provide Node를 선택하고 설치했던 환경을 입력
  • Add build step에서 Execute shell을 클릭한 뒤 script 입력창에 다음을 입력
echo ">>> build start"
cd /var/jenkins_home/workspace/{item-name}
npm install
npm run build

echo ">>> build finish"
  • 빌드 후 조치에서 Send build artifacts over SSH를 선택

publish-over-ssh

  • ssh server를 선택하고 build/ 폴더에서 build/를 삭제하고 (파일들만 이동됨.)
    원격저장소에 build-jenkins/라는 이름의 폴더 아래에 파일들이 이동된다.
  • 그 후 Exec command를 실행
    • 쉘 스크립트로 작성하였기 때문에, bash /root/workspace/frontend.sh 로 해당 쉘스크립트 실행
      #!/bin/bash
      echo "remove original build folder"
      cd /root/workspace
      rm -rf build/
      echo "rename build-jenkins to build"
      mv build-jenkins/ build/
      service nginx restart
      echo "build success"
  • 여기서 실행하는 /root/workspace/frontend.sh는 웹서버에 만들어놓은 파일임. 해당 파일을 실행함으로써 웹서버에서 특정한 동작을 하게 할 수 있음.

웹서버에서 build된 폴더를 nginx에 연결해주면 정상적으로 동작하는 것을 확인할 수 있다.

3. Docker in Jenkins

  • Jenkins에서 빌드 시 Docker를 활용한다면, Dockerfile로 build하고 build된 이미지를 dockerhub에 push, 웹 서버에서 docker pull을 받도록 해놓으면 간단하게 컨테이너를 실행할 수 있다.
  • 다만, Jenkins에서 docker를 사용하려 할 경우 jenkins에 docker를 설치해야 하고, 이 과정이 번거로울 수 있다.
  • Jenkins를 기존에 docker로 설치했고, 그 안에서 docker 명령어를 사용해야 하기 때문에 docker를 jenkins container 안에서 설치 후 docker 명령어를 사용할 수 있도록 컨테이너를 다시 설정하는 과정을 거쳐야한다.
// jenkins container 안에서 실행
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh
  • 위의 명령어로 도커를 설치해준다.
// jenkins 서버에서 실행
docker commit {container_id} jenkinsdocker //image 이름

// 기존의 젠킨스 컨테이너 삭제
docker stop {container_id}
docker rm {dontainer_id}

// docker 설치한 jenkins로 컨테이너 다시 생성
docker run -d --name jm_jenkins -p 8181:8080 -v /jenkins:/var/jenkins_home -v /var/run/docker.sock:/var/run/docker.sock -u root jenkinsdocker
  • 도커를 설치해놓은 jenkins 컨테이너로 image를 새로 만들고, docker를 사용하기 위한 설정을 한 뒤 컨테이너를 재 실행해준다. 굳이 이 과정을 거치는 이유는 로컬의 docker.sock과 컨테이너 내부의 docker.sock를 이어줘서 로컬의 도커를 내부에서도 똑같이 쓸 수 있도록 하기 위해서다.
  • 이후 컨테이너 내부에서 docker login을 실행하여 로그인한다. (Docker hub 계정 생성 후 진행)

4. spring 도커 빌드 후 도커 push

  • backend 배포의 경우 도커로 이미지를 생성한 뒤, 웹서버에서 docker image를 pull받아서 container로 만들어놓기 때문에, Dockerfile을 생성해놓고 그것을 빌드, 도커허브에 푸쉬해놓는 식으로 사용한다.

dockerhub

  • docker hub에서 repo를 생성해두어야함.
echo '>>> Deploy Start'

docker build -t {계정명}/{repo이름} .
docker push {계정명}/{repo이름}

echo '>>> Deploy Finish'
  • 현재 경로 (.)에 있는 Dockerfile로 빌드를 진행
  • dockerhub에 해당 레포에 tag는 latest로 업로드한다.
  • 쉘스크립트를 잘 짠다면, tag에 버전을 명시하여 빌드 버전을 관리할 수 있을 것.
FROM gradle:7.5.1-jdk17-alpine AS builder
WORKDIR /builder/

COPY build.gradle.kts settings.gradle.kts /builder/
RUN gradle build -x test --parallel --continue > /dev/null 2>&1 || true

COPY src /builder/src

RUN gradle clean bootJar


FROM openjdk:17-jdk-slim
COPY --from=builder /builder/build/libs/*.jar app/app.jar
COPY --from=builder /builder/build/resources app/resources

ENTRYPOINT ["java", "-jar", "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=0.0.0.0:5005", "app/app.jar", "--spring.profiles.active=develop"]
  • 위는 스프링 도커파일 예시이고, 백엔드 개발자가 작성한 것이라 잘 몰?루 참고만 하길

5. docker-compose로 백엔드 배포

  • 위에서 빌드한 도커 이미지를 웹서버에서 받아서 컨테이너로 생성해주어야 하는데, 사실 ssh로 파일 옮기는건 딱히 상관없어서 임의로 설정하면 되고, 중요한건 웹서버에서 backend.sh 파일을 실행하는 것 (도커 컨테이너 설정하는 스크립트)

backend-ssh

  • backend.sh
#!/bin/bash
cd /root/workspace
docker-compose pull
docker-compose down # --rmi all
docker-compose up -d
docker-compose push

docker ps
  • docker-compose.yml
version: "1"
services:
  backend:
    build: 
      dockerfile: Dockerfile
      context: ./backend
    image: keyme9/backend:latest
    ports:
      - "8000:8000"
    container_name: backend
  • 위의 명령어가 정상적으로 실행되려면, web 서버에 docker, docker-compose 설치하고 docker login을 해두어야 한다.
sudo snap install docker
sudo apt  install docker-compose

docker login

 

이후 빌드해보면, 컨테이너가 정상적으로 생성되는 것을 확인할 수 있다.

 

console-output

6. Trouble Shooting

  • ubuntu 22.04 버전에서 publish over ssh 설정은 뭘 해도 안됨.
    • 18.04 버전으로 낮춰서 진행해보시길 바람
  • denied: requested access to the resource is denied
    • docker 계정정보가 달라서 발생하는 문제 docker logout, docker login
  • Error response from daemon: no build stage in current context
    • Dockerfile에서 FROM이 없으면 안됨. FROM scratch라도 추가할 것
  • tag does not exist: {계정}/{레포}:{태그}
    • image에 태그를 안붙이고 push에는 tag를 붙여서 발생한 오류
반응형