[개발 서버 구축] 인스턴스 1개로 CI/CD 구축하기 1부

dOcOb·2023년 4월 18일
0

DevOps

목록 보기
1/2

1. 기본 구성


① 순서

  1. Github에 소스를 push하고, jenkins에 Webhook을 보낸다.
  2. jenkins로 github의 소스를 불러와 빌드하여, Docker hub로 image를 전송한다.
  3. jenkins의 script에서 nginx를 이용하여 두 개의 컨테이너를 롤링 업데이트를 통해 무중단 업데이트를 진행한다.

② 사전 준비물

  • AWS EC2
  • Github repository
  • Sample project

EC2가 프리티어라면 메모리가 부족할 수 있기 때문에 아래 링크의 6번을 참고하여, 가상 메모리를 사용한다.

https://backtony.github.io/spring/aws/2021-08-08-spring-cicd-1/




2. Tool 설치


① Docker 설치

참고 : https://docs.docker.com/engine/install/ubuntu/

- 저장소 설정

  1. 패키지 업데이트.
    sudo apt-get update
  2. 패키지 설치.
    sudo apt-get install ca-certificates curl gnupg
  3. Docker GPG키 설치.
  • sudo install -m 0755 -d /etc/apt/keyrings
  • curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
  • sudo chmod a+r /etc/apt/keyrings/docker.gpg
  1. Docker Repository 설정.
echo \
"deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
"$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

- 도커 엔진 설치

  1. 패키지 업데이트.
    sudo apt-get update
  2. Docker Engine, containerd 및 Docker Compose 최신 버전설치.
    sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
  • 공식 링크로 가면 특정버전 설치도 있음.
  1. 테스트용 이미지를 실행하여 Docker 엔진 설치 확인.
    sudo docker run hello-world
  2. 사용자 계정에 docker 명령어 사용 권한 추가.
    sudo usermod -aG docker $USER



② Jenkins 설치

Docker in Docker(DiD) 로 구축하여 컨테이너로 실행해도 되지만, jenkins 컨테이너 안에 Docker를 설치하는 과정에서 컨테이너를 낭비하는 것이 아닌가 하는 생각에 Bare-metal에 설치했다.

  1. 패키지 업데이트.
    sudo apt-get update
  2. java 설치.
    sudo apt-get install openjdk-11-jdk
    - 버전을 8 로 설치할 경우, jenkins 최신버전에서 실행이 안 될 수 있음.
  3. Jenkins apt-key 추가.
    wget -q -O - https://pkg.jenkins.io/debian-stable/jenkins.io.key | sudo apt-key add -
  4. Jenkins 패키지 저장소를 시스템에 추가.
    sudo sh -c 'echo deb https://pkg.jenkins.io/debian-stable binary/ > /etc/apt/sources.list.d/jenkins.list'
  5. 패키지 업데이트.
    sudo apt-get update
  6. Jenkins 설치.
    sudo apt-get install jenkins
    - 이때, E: Package 'jenkins' has no installation candidate 에러가 발생한다면, 3번 부터 다시 해보자.
  7. Jenkins 서비스 시작.
    sudo systemctl start jenkins
  8. Jenkins 서비스가 시스템 부팅 시 자동으로 시작하도록 설정합니다.
    sudo systemctl enable jenkins
  9. 초기 비밀번호 확인.
    sudo cat /var/lib/jenkins/secrets/initialAdminPassword
  10. 기본 플러그인 설치.
  11. 계정을 생성하고 로그인.



③ Nginx 설치

참고 : https://nginx.org/en/linux_packages.html#Ubuntu

  • 설치는 너무 간결하게 잘 나와있기 때문에 링크로 대체

- 설치 확인

  1. nginx 실행
    sudo systemctl start nginx
  2. 상태 확인
    sudo systemctl status nginx
    - activate (running)이 보이면 성공.




3. 기본 세팅


① dockerhub 로그인

dockerhub에서 이미지를 push/pull 하기 위해 로그인을 해둔다.

  1. 로그인 명령어.
    docker login
  2. 두 가지 항목 입력.
    Username:
    password:
    Login Succeeded 가 출력되면 성공.

② Jenkins

- 포트 변경

  1. Jenkins 중지.
    `sudo systemctl stop jenkins
  2. Jenkins 설정 파일 편집기로 열기
    sudo vi /usr/lib/systemd/system/jenkins.service
  3. port 설정 변경.

    JENKINS_PORT={포트번호}
  4. Jenkins 다시 시작.
    sudo systemctl start jenkins
  5. 보안 그룹에서 해당 포트 오픈.

- jdk setting


1. Jenkins 관리 > Global Tool Configuration > JDK

  • update-alternatives --list java 명령어를 통해 위치 확인

- git setting

  • git 설치 확인 필요.
    git --version
  1. Jenkins 관리 > Global Tool Configuration > Git installations

- maven setting

  1. maven 설치 확인.
    mvn --version
    설치가 안 되어있다면 2번
  2. Global Tool Configuration > Maven
    Add Maven 클릭 -> name 입력(Maven{version}) -> Install automatically 체크 -> save

- gradle setting

  1. gradle 설치 확인.
    gradle --version
    설치가 안 되어있다면 2번
  2. Global Tool Configuration > Gradle
    Add Gradle 클릭 -> name 입력(Gradle{version}) -> Install automatically 체크 -> save




4. 수동 무중단 배포


선행작업

  • dockerhub repository 생성.
  • Github repository 생성.



① Spring boot 프로젝트 생성

- sample project

controller

@RestController
public class TestController {
    @GetMapping("/")
    public String connectTest() {
        return "success!!!!!";
    }
}

localhost 에서 postman test

테스트 완료 후 github repository에 puash해준다.



② 프로젝트 실행, 배포

- Build project

1. gradle project

  1. git clone or pull
    git clone {repossitory}
  2. build gradle
    build.gradle이 있는 폴더로 이동하여 아래 명령어 실행.
    ./gradlew clean build -Dfile.encoding=UTF-8

2. push image

  1. build image
    Dockerfile이 있는 파일로 이동하여 아래 명령어 실행.
    docker build -t {dockerhub username}/{repository name}:{tag} .
    - 마지막에 있는 .은 사용할 Dockerfile이 있는 경로를 나타내며 .은 현재 디렉토리이다.
    - -f 옵션을 통해 원하는 경로를 지정할 수도 있다.
  2. push
    docker push {dockerhub username}/{repository name}:{tag}

3. docker run

spring1, spring2라는 이름으로 컨테이너를 2개 띄우고, 각각 9001, 9002 포트를 사용하고 프로젝트 설정에 포트를 9000으로 설정해뒀기 때문에 알맞게 publish하여 실행한다.
docker run -d -p 9001:9000 --name spring1 {image name}
docker run -d -p 9002:9000 --name spring2 {image name}

  • 실행 후 보안그룹에서 9000 포트를 열어준다.(9000번은 Nginx 입구로 쓸 예정)



③ 무중단 배포

- Nginx setting

1. nginx.conf 수정

sudo vi /etc/nginx/nginx.conf

위 그림처럼 http에 upstream 블록을 추가해준다.

  • 여러 포트를 로드밸런싱한다고 할 때 예시
http {
    upstream backend1 {
        server 7001;
        server 7002;
    }

    upstream backend2 {
        server 9001;
        server 9002;
    }

    server {
        listen 80;
        location / {
            proxy_pass http://backend1;
        }
    }

    server {
        listen 9000;
        location / {
            proxy_pass http://backend2;
        }
    }
}

2. nginx 실행

sudo systemctl start nginx

3. 테스트

postman으로 아래 항목을 모두 테스트 해준다.

  • 컨테이너 두 개 다 run 상태일 때 - 기댓값:Success
  • 컨테이너 두 개 중 하나씩 꺼져있을 때 - 기댓값:Success
  • 컨테이너 두 개 모두 꺼져있을 때 - 기댓값:Fail




5. 배포 자동화


dockerhub에서 유료 계정을 사용하면 image를 만드는 것까지 자동화 할 수 있지만,
지금은 무료로 사용중이기 때문에 jenkins를 이용해서 image도 자동 build한다.

① Jenkins setting

- Item 만들기

위에서 jenkins 플러그인들을 여러가지 다운받긴 했지만, jenkinsfile을 사용해보고 싶어졌기 때문에 Pipeline을 골라 생성해준다.


- Configure setting

General

  • GitHub project

  • 오래된 빌드 삭제 추가

Build Trigger

  • github에서 webhook을 trigger로 build시킨다.

pipeline

pipeline {
    agent any
    
    environment {
        dockerRepo = "jihoonydev/cicdtest"
        dockerTag = "0.0.1"
        githubRepo = "https://github.com/Jihoon-An/total.study.git"
        path = "test-project/"
    }
    
    stages {
        stage("Build stage") {
            steps {
                dir("${path}"){
                    echo "-------------Git pull------------"
                    sh "git pull origin main"
                    
                    echo "----------build gradle------------"
                    sh "chmod 555 ./gradlew"
                    sh "./gradlew clean build -Dfile.encoding=UTF-8"
                    
                    echo "----------delete image------------"
                    sh "docker rmi ${dockerRepo}:${dockerTag}"
                    
                    echo "-----------build image------------"
                    sh "docker build -t ${dockerRepo}:${dockerTag} ."
                    
                    echo "-----------push image-------------"
                    sh "docker push ${dockerRepo}:${dockerTag}"
                }
            }
        }
        stage("Update spring1 stage") {
            steps{
                echo "-----------stop spring1--------------"
                sh "docker stop spring1"
                echo "-----------rm spring1--------------"
                sh "docker rm spring1"
                echo "-----------run spring1--------------"
                sh "docker run -d -p 9001:9000 --name spring1 ${dockerRepo}:${dockerTag}"
            }
        }
        stage("Update spring2 stage"){
            steps{
                echo "-----------check spring1 status--------------"
                script {
                    def retryCount = 0
                    def responseCode = sh(script: "curl -s -o /dev/null -w '%{http_code}' http://localhost:9001/actuator/health", returnStdout: true).trim()
                    while (responseCode != "200" && retryCount < 30) {
                        sleep 10
                        responseCode = sh(script: "curl -s -o /dev/null -w '%{http_code}' http://localhost:9001/actuator/health", returnStdout: true).trim()
                        retryCount++
                    }
                }
                
                echo "-----------stop spring2--------------"
                sh "docker stop spring2"
                
                echo "-----------rm spring2--------------"
                sh "docker rm spring2"
                
                echo "-----------run spring2--------------"
                sh "docker run -d -p 9002:9000 --name spring2 ${dockerRepo}:${dockerTag}"
            }
        }
    }
}

- Install plugin

Github Integration 플러그인을 설치한다.



② Github Webhook setting

먼저 server에서 사용하는 github token이 아래 권한이 있는지 확인하자.

1. Add webhook

2. Webhook setting


"githun" -> ""github


③ Jenkins Credential

1. Jenkins관리에 Management Credintails

2. Add credentials

3. 정보 등록

  • Username: github username
  • password: github access token
  • ID: Credential ID



④ Jenkins workspace에 git clone

Jenkins script에 pull만 넣어놨기 때문에 clone을 미리 해둘 필요가 있다.

  • Jenkins 기본 workspace: /var/lib/jenkins/workspace/{프로젝트이름}
  1. 그냥 clone을 하면 권한이 부족해서 sudo를 붙혀서 해줬다.
  2. Jenkins 아이템마다 폴더가 따로 생겨서 item 안에 넣어준다.

어차피 지울 EC2라 하지 않았지만 계속 사용할 거라면 기존의 프로젝트 파일은 지우는게 좋다.



⑤ 자동 배포 테스트

최초의 배포는 수동으로 눌러줘야 한다길래 한번 눌러주었다.

빌드를 하니 권한이 없다고 해서 workspace에 권한을 다시 설정하고 실행한다.
chmod -R 777 /var/lib/jenkins/workspace

2부 넘어가기

하지만 계속 권한에 의한 명령어 실행 불가능으로 오류가 발생하였다.
이 전에 Jenkins container에서 DID(Docker in Docker)를 사용하여 배포하려고 하였는데,
Jenkins container 안에 docker를 설치하는 것에 실패하여 이 방법으로 했던 것이다.
2부에서는 다시 Docker in Docker를 사용하여,Jenkins container에서 spring container를 두 개 run하여 사용하는 방법으로 배포해보자.

profile
반은 해야 시작이다.

0개의 댓글