Jenkins 비정상 종료 시 자동 시작하는 systemd 서비스 파일

테스트 환경

  • NHN Cloud
  • Ubuntu 20.04 LTS
  • Docker 23.0.3
  • Jenkins 2.399
  • Python 3.9

 

상황

Jenkins가 비정상적으로 종료되었을 경우, Jenkins가 자동 실행되게 하도록 합니다.

 

해결방법

  • sudo systemctl enable jenkins 명령어 실행
    • Failed to enable unit: Unit file jenkins.service does not exist.와 같은 오류가 발생합니다.
  • Jenkins Docker 컨테이너를 자동 시작하는 systemd 서비스 파일을 생성하여 적용해줍니다.
  1. /usr/lib/systemd/system 디렉토리에 jenkins.service파일 생성
sudo vi /usr/lib/systemd/system/jenkins.service

 

2. 파일 내용 작성

[Unit]
Description=Jenkins container
Requires=docker.service
After=docker.service

[Service]
Restart=on-failure
Group=docker
ExecStart=/usr/bin/docker start -a jenkins
ExecStop=/usr/bin/docker stop -t 2 jenkins

[Install]
WantedBy=multi-user.target

Jenkins Docker 컨테이너를 관리할 새 systemd 서비스인 jenkins를 정의합니다.

 

서비스 파일에는 세 개의 부분이 있습니다.

  • [Unit]: 서비스가 필요로하는 모든 항목을 나열합니다. 적용하기 위해서는 Docker 서비스가 실행 중이어야 합니다.
  • [Service] :
    • Restart=on-failure : 서비스가 어떤 이유로 실패하거나 중지되면 자동으로 다시 시작되어야함을 지정
      • 서비스가 비정상적으로 종료되었을 때에만 동작함. 호스트 머신이 강제 종료되는 경우는 시스템 수준의 이벤트로서 서비스 자체의 종료와는 다르기 때문에 일반적으로 호스트 머신이 강제 종료될 때는 systemd가 서비스를 재시작하지 않음. -> 데몬 감시 스크립트 등록이 필요할 것으로 생각됨
    • Group : irteam, irteamsu, root 사용자 모두 실행 가능하도록 Group 설정
    • ExecStart : Jenkins 컨테이너를 시작하는 명령을 지정
    • ExecStop : Jenkins 컨테이너를 중지하는 명령을 지정
  • [Install]: 서비스가 시작될 때, 이 경우 multi-user.target 레벨에서 시작됨을 지정

 

3. 새 서비스 파일을 인식하도록 systemd 데몬을 다시 로드합니다.

sudo systemctl daemon-reload

서비스 파일에 대한 변경 사항을 인식하도록 systemd 데몬을 다시 로드하는 것입니다.

 

4. Jenkins 서비스를 부팅 시 자동 시작하도록 설정합니다.

sudo systemctl enable jenkins

 

결과

  1. Jenkins 비정상 종료 시 Jenkins 도커 컨테이너가 자동으로 실행되는 것을 확인하기
  • SIGABRT 로 비정상 종료 됨

 

  • 자동 재실행 됨

 

  • 로그

 

2. 호스트 머신 비정상 종료(reboot) 시 Jenkins 도커 컨테이너가 자동으로 실행되는 것을 확인하기

  • systemctl enable 명령어로 reboot 가능 확인!

 

systemd 서비스 파일 설정

[Service]

Restart=[no|on-success|on-failure|on-watchdog|on-abort|always]

  • 해당 유닛이 죽었을 때나 혹은 WatchdogSec만큼의 시간 동안 응답이 없는 경우 재시작한다.
  • no (기본값) : 유닛을 다시 시작하지 않는다.
  • on-success: 유닛이 정상적으로 종료 되었을 때만 재시작한다.
    • 종료 시 '0' 값을 리턴하여 종료 되었거나 SIGHUP, SIGINT, SIGTERM, SIGPIPE 등과 같은 시그널 또는 SuccessExitStatus 설정에서 지정된 리턴 코드 목록에 따른 시그널에 대해서 모두 성공으로 인식해 재시작 하게 된다.
  • on-failure : 유닛이 비정상적으로 종료 되었을 때 재시작한다.
    • 리턴값이 '0' 이 아닌 경우, core dump 와 같이 비정상적인 시그널을 받고 종료된 경우, 타임 아웃값 내 응답이 없는 경우 등 재시작한다.
  • on-watchdog : WatchdogSec 에 설정된 시간 내 응답이 없는 경우에만 재시작한다.
  • on-abort : 지정되지 않은 리턴값을 받은 경우 재시작한다.
  • always : 종료 상태 등과 무관하게 무조건 재시작한다.
    • 사용자가 중지해도 시스템이 다시 띄워지게 되므로 설정된 유닛 중지 시 주의 필요

시스템 리소스 관련 Limit 설정

  • sudo systemctl show jenkins | grep ^Limit : 리소스별 설정된 Limit 값 목록 조회

 

  • LimitAS
    • 서비스에서 사용할 수 있는 최대 가상 메모리 공간
    • infinity : 서비스가 필요한 만큼의 가상 메모리를 사용할 수 있도록 제한하지 않음
  • LimitRSS
    • 서비스에서 사용할 수 있는 최대 물리 메모리 공간
    • infinity : 서비스가 필요한 만큼의 RAM을 사용할 수 있도록 제한하지 않음
  • LimitCORE
    • 충돌 발생 시 서비스에서 생성할 수 있는 코어 덤프 파일의 최대 크기
    • infinity : 코어 덤프 파일 크기 제한하지 않음
  • LimitNOFILE
    • 서비스가 동시에 가질 수 있는 열린 파일(파일 디스크립터)의 최대 개수
    • 1048576 : 최대 1048576개의 열린 파일을 동시에 가질 수 있음

 

최대 오픈 파일 수 설정

  • 사용자 계정에 대한 리소스 제한을 설정하는 시스템 구성 파일
  • 개별 사용자나 프로세스가 시스템 자원을 독점하는 것을 방지하고, 자원 할당을 보장하기 위해 사용
/etc/security/limits.conf

 

  • <domain> : 제한이 적용되는 범위를 지정
    • @그룹명
    • 사용자명
    • * : 모든 사용자
  • <type> : 제한의 유형 지정
    • soft : 일시적으로 초과할 수 있는 제한 값
    • hard : 초과할 수 없는 최대 값
  • <item> : 제한되는 자원 지정
    • core : 코어 파일의 최대 크기
    • data : 최대 데이터 크기
    • fsize : 최대 파일 크기
    • memlock : 최대 잠긴 메모리 주소 공간(물리적 RAM에 잠긴 상태로 유지하는 메모리 양, 디스크로 스왑되지 않고 빠른 액세스가 필요할 때)
    • nofile : 최대 개방 파일 수
    • rss : 최대 레지던트 세트 크기(프로세스가 실제 RAM에 보유하고 있는 메모리의 일부)
    • stack : 최대 스택 크기
    • cpu : 최대 CPU 시간
    • nproc : 최대 프로세스 수
    • as : 주소 공간 제한
    • maxlogins : 해당 유저의 최대 로그인 수
    • priority : 유저 우선순위
    • locks : 유저가 가지고 있을 수 있는 최대 파일 개수
    •  
  • <value> : 지정된 자원의 제한 값 정의
  • 설정 내용 적용하는 방법 2가지
    • 1. 재부팅하기
      1. ulimit -a 로 리소스 재로드하기
ulimit -a

Docker로 Jenkins를 띄우려고 했는데, 이미 8080 포트를 사용하고 있어 Jenkins 포트 번호를 바꾸고자 했다.

 

만약 Docker가 아닌 로컬에 Jenkins를 설치했다면 "/etc/default/jenkins" 파일을 수정해야 한다는 정보는 많지만

Docker의 Jenkins 기본 포트를 변경하는 정보는 많지 않았다.

 

🐳 Jenkins Docker 공식문서

https://hub.docker.com/_/jenkins

 

jenkins - Official Image | Docker Hub

DEPRECATION NOTICE This image has been deprecated for over 2 years in favor of the jenkins/jenkins:lts image provided and maintained by the Jenkins Community as part of the project's release process. The images found here have not received updates for over

hub.docker.com

 

Docker의 Jenkins 기본 포트를 변경하려면 JENKINS_OPTS를 활용해야 한다.

처음에 그냥 무작정 7070:7070만 입력했더니 젠킨스 접속이 안 되더라..

docker run -itd --env JENKINS_OPTS=--httpPort=7070 -p 7070:7070 -v ~/jenkins:/var/jenkins_home --name jenkins jenkins/jenkins:lts

JENKINS_OPTS 환경변수에 --httpPort값을 지정해주면 된다!

 

 

 

 

참고 사이트

1. 문제 상황

Jenkins POST API를 호출하니까 403 No valid crumb was included in the request 에러가 발생하였다.

 

2. 해결 방법

Jenkins Configure → Script Console

import jenkins.model.Jenkins
def instance = Jenkins.instance
instance.setCrumbIssuer(null)

스크립트를 작성 후 실행한다. 실행 후에는 Jenkins를 재시작해주어야 한다.

 

 

 

참고 사이트

배치를 수동으로 돌려야 하는 상황이 있을 수 있다.

예를 들면, DBMS에 새 버전이 나왔을 때 바로 배치를 돌려서 데이터를 추가해야 될 수도 있기 때문! 

 

웹에 [수동 배치] 같은 버튼을 만들어 놓고 누르면 젠킨스에서 배치가 돌게 하고 싶은 경우

젠킨스에서 제공하는 원격으로 빌드 유발 기능을 사용하면 된다.

 

그러면 젠킨스에 설정해 놓은 재시도 횟수, 두레이 메신저 알림 기능, 로그 확인 기능을 모두 사용할 수 있기 때문이다.

 

아래 방법을 따라서 원격으로 빌드를 유발해보자!

 


1. 토큰 발급

  • Dashboard > 사람

 

  • admin이란 이름으로 사용자를 등록했기 때문에 admin 클릭!

 

  • 설정으로 들어가서 API Token 발급

다시 들어가면 값이 사라져 있으니 잘 복사해두자!

 

2. Job 설정

  • 원격으로 빌드 유발 체크 및 토큰 이름 설정

토큰 이름은 나중에 호출할 때 사용할 것이기 때문에 띄어쓰기 없이 짧게 적어주면 된다.

 

3. 원격으로 빌드

1) Job Build

  • Parameter가 없는 build
curl -i -X POST 'http://<jenkins_username>:<jenkins_api_token>@<jenkins_url>/job/<job_name>/build?token= <token_name>'

 

값을 채워넣고 터미널에서 실행해보면 젠킨스 job이 잘 실행된다!

 

파이썬에서 구현하고자 하면

@api_view(['POST'])
def trigger_build(request):
    # 젠킨스 빌드 주소 - 파라미터 : 토큰
    jenkins_url = f"http://{jenkins_username}:{jenkins_password}@{jenkins_server}/job/{jenkins_job_name}/build?token={jenkins_token}"

    try:
        response = requests.post(jenkins_url)  # 젠킨스 빌드 주소로 post 요청 보내기

        if response.status_code == 201:  # 요청 성공
            print("[Success] Jenkins trigger builds remotely!")
            return HttpResponse("Jenkins build triggered successfully!", status=201)
        else:  # 요청 실패
            print("[Fail] Failed to trigger builds remotely. Status code:", response.status_code)
            return HttpResponse("Failed to trigger Jenkins build. Status code: " + str(response.status_code), status=500)

    except Exception as e:  # 예외 발생
        print("[Fail] Failed to trigger builds remotely. Exception:", e)
        return HttpResponse("Failed to trigger Jenkins build. Exception: " + str(e), status=500)

 

  • Build with Parameter
curl -X POST 'http://[IP]:[PORT]/job/[JOB_NAME]/build buildWithParameters --data [PARAM1]=[VALUE1] --data [PARAM2]=[VALUE2] --user [USER_NAME]:[USER_API_TOKEN]'

 

@api_view(['GET'])
def trigger_build_with_parameters(request):
    try:
        # 젠킨스 빌드 주소 - 파라미터 : 토큰, 파라미터
        jenkins_url = f"http://{jenkins_username}:{jenkins_password}@{jenkins_server}/job/{jenkins_job_name}/buildWithParameters?token={jenkins_token}&params={params}"
        response = requests.post(jenkins_url)  # 젠킨스 빌드 주소로 post 요청 보내기

        if response.status_code == 201:  # 요청 성공
            print("[Success] Jenkins trigger builds remotely!")
            return HttpResponse("Jenkins build triggered successfully!", status=201)
        else:  # 요청 실패
            print("[Fail] Failed to trigger builds remotely. Status code:", response.status_code)
            return HttpResponse("Failed to trigger Jenkins build. Status code: " + str(response.status_code), status=500)

    except Exception as e:  # 예외 발생
        print("[Fail] Failed to trigger builds remotely. Exception:", e)
        return HttpResponse("Failed to trigger Jenkins build. Exception: " + str(e), status=500)

 

2) Job Build 조회

curl -X GET 'http://[IP]:[PORT]/job/[JOB_NAME]/api/json --user [USER_NAME]:[USER_API_TOKEN]'

 

3) Job Build 결과 조회

 curl -X GET 'http://[IP]:[PORT]/job/[JOB_NAME]/[BUILD_NUM]/api/json --user [USER_NAME]:[USER_API_TOKEN]'

 

빌드 넘버를 확인하기 어려울 경우

BUILD_NUM 대신 lastStableBuild를 입력할 경우 가장 최신의 빌드 결과를 알 수 있다.

각 조회 결과는 json, xml, python 타입을 지정할 수 있다.

 curl -X GET 'http://[IP]:[PORT]/job/[JOB_NAME]/lastStableBuild/api/json --user [USER_NAME]:[USER_API_TOKEN]'

 

 

 

 

 

 

참고 사이트

설치 환경

  • NHN Cloud
  • Ubuntu 20.04 LTS
  • Jenkins 2.397

 

⚠️ 주의 !

2023년 3월 28일부터 Linux 설치 패키지에 대한 새로운 레포지토리 서명 키를 사용합니다.

Jenkins 2.397 설치하기 전에 새 서명 키로 설치해야 합니다!

 

구글링 했을 때 나오는 정보들은 예전 거여서 무조건 에러가 발생하니까 당황하지 말고 공식 문서를 참고합시다..!

 

Jenkins 2.397 and 2.387.2: New Linux Repository Signing Keys

Update Red Hat compatible operating systems (Red Hat Enterprise Linux, Alma Linux, CentOS, Fedora, Oracle Linux, Rocky Linux, Scientific Linux, etc.) with the command: Red Hat/CentOS $ sudo rpm --import https://pkg.jenkins.io/redhat/jenkins.io-2023.key

www.jenkins.io

 

설치 과정 

1. 시스템에 젠킨스 레포지토리 추가

curl -fsSL https://pkg.jenkins.io/debian/jenkins.io-2023.key | sudo tee \
  /usr/share/keyrings/jenkins-keyring.asc > /dev/null
echo deb [signed-by=/usr/share/keyrings/jenkins-keyring.asc] \
  https://pkg.jenkins.io/debian binary/ | sudo tee \
  /etc/apt/sources.list.d/jenkins.list > /dev/null

 

2. apt 업데이트

sudo apt update

 

3. 젠킨스 설치

sudo apt install jenkins

 

4. 접속

http://{인스턴스 IP}:8080

 

⚠️ 접속이 안 된다면 ?

  • 보안그룹에 8080 포트 열어주었는지 확인
  • jenkins status 확인
# jenkins status 확인
sudo systemctl status jenkins

# jenkins 시작
sudo systemctl start jenkins
  • 8080 포트 열려있는지 확인
sudo ufw status

# Status: inactive 라면
sudo ufw allow 8080

sudo ufw allow OpenSSH

sudo ufw enable

sudo ufw status

 

5. 설정

  • 초기 비밀번호 위치 : /var/lib/jenkins/secrets/initialAdminPassword

 

6. 완료

설정 단계를 차례대로 진행하다 보면..

젠킨스 웹 인터페이스 접속 완료!

+ Recent posts