본문 바로가기

인프라/MW

Jenkins

젠킨스에 대해서 간략하게 소개를 하도록하겠다.

젠킨스는  CI/CD를 관리해주는 우리의 집사님이다.

 

jenkins.png

 

평소에는 성실해서 언제나 웃어주시지만,

 

fire.png

 

한번 열받으면 무서운 분이니 조심하자.

 

 

젠킨스에 대한 상세한 내용은 젠킨스를 참고하면 되겠다.

 

이야기를 진행하여, 초기 CI/CD라는 개념이 확립되지 않았을 당시에는 모든 형상 관리를 수동으로 관리를 해왔다는 이야기가 전해진다.(당시에 헬이었다고 한다.)

 

하지만, 우리의 집사 아저씨가 취직해주시면서 신세계가 열렸다는 이야기로 전해진다.

(젠킨스를 이용해도 간혹, 형상 충돌이 발생하는데 당시에 한번 꼬이면, 푸는데 엄청 시간이 들었다고함)

 

잡다한 이야기는 여기까지하고, jenkins를 접하면서 만나볼 내용에 대해서 차례대로 기술하겠다.

 


설치방법

더보기

1. 다운로드/설치 위치를 생성한다.

export ID=`whoami`
export BASE_DIR=`cat /etc/passwd|grep ${ID} |cut -f6 -d ':'`

mkdir ${BASE_DIR}/appsw

mkdir ${BASE_DIR}/jenkins_home

 

2. jre다운로드 받기/ 명칭변경하기

cd ${BASE_DIR}/appsw

wget https://javadl.oracle.com/webapps/download/AutoDL?BundleId=239848_230deb18db3e4014bb8e3e8324f81b43

mv "AutoDL?BundleId=239848_230deb18db3e4014bb8e3e8324f81b43" jre-8u221-linux-x64.tar.gz

tar -xzf jre-8u221-linux-x64.tar.gz 

 

3. tomcat 받기 / jenkins.war 받기

wget https://archive.apache.org/dist/tomcat/tomcat-9/v9.0.26/bin/apache-tomcat-9.0.26.tar.gz

tar -xzf apache-tomcat-9.0.26.tar.gz

cd ${BASE_DIR}/appsw/apache-tomcat-9.0.26/webapps

wget https://updates.jenkins-ci.org/latest/jenkins.war

 

4. tomcat 환경변수 설정하기(setenv.sh)

vi ${BASE_DIR}/appsw/apache-tomcat-9.0.26/bin/setenv.sh

#!/bin/bash

echo "Setting parameters from $CATALINA_BASE/bin/setenv.sh" 
echo "_______________________________________________" 

# Default Ports 
export HTTP_PORT=8080 
export HTTPS_PORT=8443 
export AJP_PORT=8009 
export SHUTDOWN_PORT=8005 

export ID=`whoami`
export BASE_DIR=`cat /etc/passwd|grep ${ID} |cut -f6 -d ':'`
export JAVA_HOME=${BASE_DIR}/appsw/jre1.8.0_221
export JENKINS_HOME=${BASE_DIR}/jenkins_home

# The hotspot server JVM has specific code-path optimizations 
# which yield an approximate 10% gain over the client version. 
export CATALINA_OPTS="$CATALINA_OPTS -server" 

# discourage address map swapping by setting Xms and Xmx to the same value 
# http://confluence.atlassian.com/display/DOC/Garbage+Collector+Performance+Issues 
export CATALINA_OPTS="$CATALINA_OPTS -Xms512m -Xmx512m" 

# Increase maximum perm size for web base applications to 4x the default amount 
# http://wiki.apache.org/tomcat/FAQ/Memoryhttp://wiki.apache.org/tomcat/FAQ/Memory 
export CATALINA_OPTS="$CATALINA_OPTS -XX:MaxPermSize=256m" 
 
# Oracle Java as default, uses the serial garbage collector on the 
# Full Tenured heap. The Young space is collected in parallel, but the 
# Tenured is not. This means that at a time of load if a full collection 
# event occurs, since the event is a 'stop-the-world' serial event then 
# all application threads other than the garbage collector thread are 
# taken off the CPU. This can have severe consequences if requests continue 
# to accrue during these 'outage' periods. (specifically webservices, webapps) 
# [Also enables adaptive sizing automatically] 
export CATALINA_OPTS="$CATALINA_OPTS -XX:+UseParallelGC" 
 
# This is interpreted as a hint to the garbage collector that pause times 
# of <nnn> milliseconds or less are desired. The garbage collector will 
# adjust the  Java heap size and other garbage collection related parameters 
# in an attempt to keep garbage collection pauses shorter than <nnn> milliseconds. 
# http://java.sun.com/docs/hotspot/gc5.0/ergo5.html 
export CATALINA_OPTS="$CATALINA_OPTS -XX:MaxGCPauseMillis=1500" 

# Verbose GC 
export CATALINA_OPTS="$CATALINA_OPTS -verbose:gc" 
export CATALINA_OPTS="$CATALINA_OPTS -Xloggc:$CATALINA_BASE/logs/gc.log" 
export CATALINA_OPTS="$CATALINA_OPTS -XX:+PrintGCDetails" 
export CATALINA_OPTS="$CATALINA_OPTS -XX:+PrintGCDateStamps" 
export CATALINA_OPTS="$CATALINA_OPTS -XX:+PrintGCApplicationStoppedTime" 
 
# Disable remote (distributed) garbage collection by Java clients 
# and remove ability for applications to call explicit GC collection 
export CATALINA_OPTS="$CATALINA_OPTS -XX:+DisableExplicitGC" 
 
# Prefer IPv4 over IPv6 stack 
export CATALINA_OPTS="$CATALINA_OPTS -Djava.net.preferIPv4Stack=true" 
 
# Set Java Server TimeZone to UTC 
export CATALINA_OPTS="$CATALINA_OPTS -Duser.timezone=GMT+9" 
 
# IP ADDRESS OF CURRENT MACHINE 
if hash ip 2>&- 
then 
    IP=`ip addr show | grep 'global eth[0-9]' | grep -o 'inet [0-9]\+.[0-9]\+.[0-9]\+.[0-9]\+' | grep -o '[0-9]\+.[0-9]\+.[0-9]\+.[0-9]\+'` 
else 
    IP=`ifconfig | grep 'inet [0-9]\+.[0-9]\+.[0-9]\+.[0-9]\+.*broadcast' | grep -o 'inet [0-9]\+.[0-9]\+.[0-9]\+.[0-9]\+' | grep -o '[0-9]\+.[0-9]\+.[0-9]\+.[0-9]\+'` 
fi 

# Jenkins_Path(use or not)
export CATALINA_OPTS="$CATALINA_OPTS -DJENKINS_HOME=$JENKINS_HOME" 

# Check for application specific parameters at startup 
if [ -r "$CATALINA_BASE/bin/appenv.sh" ]; then 
  . "$CATALINA_BASE/bin/appenv.sh" 
fi 
 
# Specifying JMX settings 
if [ -z $JMX_PORT ]; then 
    echo "JMX Port not specified. JMX interface disabled.\n" 
else 
        echo "JMX interface is enabled on port $JMX_PORT\n" 
# Consider adding -Djava.rmi.server.hostname=<host ip> 
        export CATALINA_OPTS="$CATALINA_OPTS -Dcom.sun.management.jmxremote \ 
            -Dcom.sun.management.jmxremote.port=$JMX_PORT \ 
            -Dcom.sun.management.jmxremote.ssl=false \ 
            -Dcom.sun.management.jmxremote.authenticate=false \ 
            -Djava.rmi.server.hostname=$IP" 
fi 
 
# Export ports 
export CATALINA_OPTS="$CATALINA_OPTS -Dport.http=$HTTP_PORT" 
export CATALINA_OPTS="$CATALINA_OPTS -Dport.shutdown=$SHUTDOWN_PORT" 
export CATALINA_OPTS="$CATALINA_OPTS -Dport.https=$HTTPS_PORT" 
export CATALINA_OPTS="$CATALINA_OPTS -Dport.ajp=$AJP_PORT" 
 
export JAVA_ENDORSED_DIRS="$CATALINA_BASE/endorsed:$CATALINA_HOME/endorsed" 
 
#export CLASSPATH="$CATALINA_BASE/lib/logback-core-1.1.1.jar:$CLASSPATH" 
#export CLASSPATH="$CATALINA_BASE/lib/logback-classic-1.1.1.jar:$CLASSPATH" 
#export CLASSPATH="$CATALINA_BASE/lib/slf4j-api-1.7.6.jar:$CLASSPATH" 
#export CLASSPATH="$CATALINA_BASE/lib/jul-to-slf4j-1.7.6.jar:$CLASSPATH" 

echo "Using CATALINA_OPTS:" 
for arg in $CATALINA_OPTS 
do 
    echo ">> " $arg 
done 
echo "" 
   
echo "Using JAVA_OPTS:" 
for arg in $JAVA_OPTS 
do 
    echo ">> " $arg 
done 

echo "_______________________________________________" 
echo ""

 

5. 젠킨스 기동/확인 & 초기 패스워드 확인

cd ${BASE_DIR}/appsw/apache-tomcat-9.0.26/bin

sh startup.sh

ps -ef | grep apache-tomcat

cat ${BASE_DIR}/jenkins_home/secrets/initialAdminPassword

 

6. 브라우저 확인

 

http://{설치된서버IP}:8080/jenkins 호출 시, 초기 패스워드 입력란에 기입 후 진행

 

사용할 플러그인 설치진행

 

계정생성

 

모든 task 완료 후, 접속.

 

설치완료

 


어드민 계정 패스워드 분실/초기화

더보기

※ 패스워드 정책 적용

현재, 보안정책 적용이 되어 있지 않아 테스트를 위한 해당단계를 거침.

 

1. 젠킨스 설정파일 백업 / 수정

export ID=`whoami`; export BASE_DIR=`cat /etc/passwd|grep ${ID} |cut -f6 -d ':'`

cd ${BASE_DIR}/jenkins_home

cp config.xml config.xml_`date +%Y%m%d`

vi ${BASE_DIR}/jenkins_home/config.xml

 

config.xml 내, 설정정보 코드삭제(정책적용되었을 시, 계정관련 권한 내용이 더 존재함)

 

  <useSecurity>true</useSecurity>
  <authorizationStrategy class="hudson.security.FullControlOnceLoggedInAuthorizationStrategy">
    <denyAnonymousReadAccess>true</denyAnonymousReadAccess>
  </authorizationStrategy>
  <securityRealm class="hudson.security.HudsonPrivateSecurityRealm">
    <disableSignup>true</disableSignup>
    <enableCaptcha>false</enableCaptcha>
  </securityRealm>

 

보안코드 제거 후, config.xml

 

2. tomcat 재기동

cd ${BASE_DIR}/appsw/apache-tomcat-9.0.26/bin
./shutdown.sh
./startup.sh

 

설정 전, 로그인화면

 

설정 후, 로그인 화면(로그인절차 무시)

 

3. 신규 정책 추가(어드민권한계정 추가생성)

Jenkins 관리 > Configure Global Security > 신규정책 생성

 

 

로그인 페이지 > 신규 어드민 계정생성

   

4. 설정 취합 & 재기동

vi ${BASE_DIR}/jenkins_home/config.xml

 

config.xml 내, admin 관련 권한 내용 복사

  

vi ${BASE_DIR}/jenkins_home/config.xml_`date +%Y%m%d`


  <authorizationStrategy class="hudson.security.GlobalMatrixAuthorizationStrategy">
	복사한 내용 붙여 넣기
	... 중간생략 ...
  </authorizationStrategy>

 

cp ${BASE_DIR}/jenkins_home/config.xml_`date +%Y%m%d` ${BASE_DIR}/jenkins_home/config.xml

 

cd ${BASE_DIR}/appsw/apache-tomcat-9.0.26/bin
./shutdown.sh
./startup.sh

 

5. 신규 어드민 계정으로 로그인 & 패스워드 변경

신규 어드민계정 접속 > Jenkins 관리 > Manage Users > Password 변경

플러그인 설치(사내망)

더보기

1. 하단의 References를 참조하며,

   필요한 모듈과 대상 모듈에서 참조로 사용하는 의존성모듈들을 검토하여 전부 받아서 반입하여야한다.

 

2. 반입 후, 서버를 통한 플러그인 업로드 or 브라우저를 통한 플러그인 업로드.

 

#- 서버

진행순서 : 1. sftp를 이용한 파일 업로드 & tomcat재기동
                 path : ${BASE_DIR}/jenkins_home/plugins

 

#- 브라우저

진행순서 : 1. Jenkins관리 > 플러그인 관리 > 고급 > 플러그인 올리기

                 > 설치완료 후, 하단 체크박스 선택 > ㅁ설치가 끝나고 실행중인 작업이 없으면 Jenkins 재시작.

임시모듈 업로드(올리기)

 

임시모듈 업로드 후, 하단 체크박스를 통한 재기동 시 적용완료

 


Jenkins In Docker(Install/Blue Ocean)

더보기

1. Docker설치 전, 사전작업(계정생성/패스워드)

#- docker 계정 생성
export ID=docker; export PASS=docker1234
sudo useradd $ID -m -s /bin/bash

#- 현재 접속 계정이 root일 시
sudo echo $PASS | passwd --stdin $ID

#- 현재 접속 계정이 root가 아닐 시
sudo passwd $ID
docker1234

 

계정생성 & 패스워드적용

 

sudo vi /etc/sudoers

#- 계정권한조율은 References(Sudoers)를 참고
설정이유 : * docker 계정으로 package 설치를 편하게 하기위해
          * 설정 하고 싶지 않을 시, root권한으로 작업 진행
docker  ALL=(ALL)       NOPASSWD:ALL

 

sudo 사용권한 추가(docker계정)

 

※ 다른계정으로 추가시, 하단참조

#- 계정 생성단계 진행불필요(그룹만 생성)
sudo groupadd docker

#- 사용하고자 하는 계정에서, 대상 docker그룹맵핑
sudo usermod -aG docker $USER

#- 그룹권한 현행화
sudo newgrp docker

 

2. Docker설치 전, 사전작업(OS Package Update)

sudo yum update -y

 

OS 내, 설치된 Package 업데이트 진행

 

3. Docker 설치

sudo yum install docker -y

 

Docker 설치 진행

 

4.  Docker 설정

#- chkconfig 목차 등록 시, systemd로 대상 서비스 FWD  
sudo chkconfig docker on
  -> systemctl enable docker.service

 

systemd 내, docker 서비스 등록(docker.override실행 불필요)

 

5. Docker 기동 

#- Docker 기동 및 프로세스 확인
sudo systemctl start docker
ps -ef | grep docker

#- Docker 정상 실행 여부확인
sudo docker run hello-world

 

Docker기동&확인&기능확인

 

6. Docker 상태확인

sudo systemctl status docker

 

Docker 서비스 상태확인

 

7. Docker remote access설정

#- Nano를 통한 서비스 등록
$ sudo systemctl edit docker.service

[Service]
ExecStart=
ExecStart=/usr/bin/dockerd -H fd:// -H tcp://127.0.0.1:2375

#- 나노 사용하기 불편할 시, 하단 vi편집기를 이용하여 등록
vi /etc/systemd/system/docker.service.d/override.conf

[Service]
ExecStart=
ExecStart=/usr/bin/dockerd -H fd:// -H tcp://127.0.0.1:2375

 

/etc/systemd/system/docker.service.d/override.conf (vi 편집기를 추천)

 

#- Docker 설정 로딩
sudo systemctl daemon-reload

#- Docker 서비스 재실행
sudo systemctl restart docker.service

#- Docker 서비스 확인
sudo netstat -lntp | grep dockerd

 

 ※주의사항

 공식 사이트에서 remote access 관련설정 시, /etc/docker/daemon.json 과 같이 7. Docker remote access설정 작업을 진행 시, 오류 발생하니 둘중 하나만 사용한다.

override.conf 와 daemon.json 을 같이 등록 시, 오류발생. 택1 설정진행

 

/etc/docker/daemon.json

{
"hosts": ["unix:///var/run/docker.sock", "tcp://127.0.0.1:2375"]
}

 

8. Docker 설치 후, 검토/트러블 슈팅

#- Docker 설정 체크 스크립트 받기
curl https://raw.githubusercontent.com/docker/docker/master/contrib/check-config.sh > check-config.sh

#- Docker 설정 체크
bash ./check-config.sh

 

check-config.sh 실행 시, Storage missing 같은 것은 무시해도된다.

 

9. Jenkins파란바다(?) 기동 스크립트 작성&실행

#- 스크립트 작성
vi docker-start.sh

#!/bin/bash

docker run \
 -d \
 --rm \
 -u root \
 -p 8080:8080 \
 -v jenkins-data:/var/jenkins_home \
 -v /var/run/docker.sock:/var/run/docker.sock \
 -v "$HOME":/home \
 --name jenkins \
 jenkinsci/blueocean
 
 #- 스크립트 실행
 sh docker-start.sh
 
 #- 프로세스확인
 ps -ef | grep jenkins

 

docker-start.sh 실행 시, 자동 jenkins 이미지 pull 진행, loading 시, 자차 snap-shot 이미지 보관

 

스크립트 실행 및 프로세스 확인

 

docker logs jenkins

 

docker logs jenkins 명령어를 통한 초기 젠킨스 패스워드 입수

 

10. Jenkins 브라우저 확인

초기패스워드 입력 접속

 

어드민 계정 패스워드 변경을 위한 Administration 탭 접근

 

Jenkins > Manage Users 접근(패스워드 변경)

 

Jenkins>Jenkins own user database> admin (패스워드변경) > Save(클릭)

 

변경 후, 저장시 정상적으로 노출되는 페이지니 당황하지말고 url을 재호출

 

http://${my.server.ip}:8080

 

플러그인 설치 화면에서, 본인이 원하는 선택지 선택.(저자는 추천설치)

 

플러그인 설치화면

 

인스턴스 설정 화면(URL정의) > Save and Finish

 

변경한 패스워드로 로그인 시도(완료)

 

※추가사항-1( 젠킨스 재시작 또는 정지를 원할 때, 참조 )

#- 젠킨스 재시작
docker restart jenkins

#- 젠킨스 정지(시작은 스크립트이용)
docker stop jenkins

 

※추가사항-2( 설치스크립트 )

docker에서 지원하는 자동 설치 스크립트가 존재하나, aws에서 지원하지 않기에 해당 스크립트로 설치 진행을 기재 못함. 

#- 스크립트 다운로드
curl -fsSL https://get.docker.com -o get-docker.sh

#- 스크립트 실행
sudo sh get-docker.sh


- [에러내용] -> ERROR: Unsupported distribution 'amzn'

 

※스크립트 추가(컨트롤링 스크립트)(191115)

# vi jenkins-ctrl

#!/bin/bash

#--------------------------------------------------------------------------
#-- Set Error if Variable is Not Set
#-- Set Exit if Error is Occured
#--------------------------------------------------------------------------

#set -o nounset
set -o errexit

#--------------------------------------------------------------------------
# // Check Parameter & Define Variable
#--------------------------------------------------------------------------

if [[ $# -lt 1 ]]; then
    if [[ $# != $1 ]]; then
        echo
        echo " *** NOT FOUND CMD : $1  ***"
        echo " ex ) ./jenkins-ctrl (start or stop or log)"
        echo
        exit 1
    fi
fi

CMD=$1
LOG_DIR=${SCRIPT_DIR}/logs
CMD_LIST=("start" "stop" "log")
DIR_LIST=("${LOG_DIR}")
ST_LIST=("0" "1")

DATE=`date +%Y%m%d-%H%M%S`
APP_NAME="jenkins"
UNAME=`id -u -n`
JEN_USER="cicd"
PID=`ps -ef | grep -w ${APP_NAME} | grep -v "grep" | grep -v "jenkins-ctrl" | awk '{print $2}'`

#---------------------------------------------------------------
# // Validate Parameter & Run Script
#---------------------------------------------------------------

func_jenkins_start() {
  docker run \
  -d \
  --rm \
  -u root \
  -p 8080:8080 \
  -v jenkins-data:/var/jenkins_home \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v "$HOME":/home \
  --name jenkins \
  jenkinsci/blueocean
}

func_jenkins_stop() {
    docker stop jenkins
}

func_jenkins_log() {
    docker logs jenkins
}

func_output() {
    PID=$1
    if [ -z $PID ]; then
        echo "[ INFO ] ${APP_NAME} is not running."
    else
        echo "[ INFO ] ${APP_NAME} is running."
        echo "[ INFO ] ${APP_NAME} PID is $PID."
    fi
}

func_pid_chk() {
    args=$1
    if [[ e$args != "e" ]]; then
        echo "0"
    else
        echo "1"
    fi
}

func_cmd_ctrl() {
    if [[ ${CMD} = "${CMD_LIST[0]}" ]]; then
        status=$(func_pid_chk ${PID})
        if [[ ${status} = "${ST_LIST[0]}" ]]; then
            func_output ${PID}      
            exit 0;
        elif [[ ${status} = "${ST_LIST[1]}" ]]; then 
            func_output ${PID}
            echo "[ INFO ] ${APP_NAME} start now."
            func_jenkins_start
            exit 0;
        fi
    elif [[ ${CMD} = "${CMD_LIST[1]}" ]]; then
        status=$(func_pid_chk ${PID})
        if [[ ${status} = "${ST_LIST[0]}" ]]; then
            func_output ${PID}
            echo "[ INFO ] ${APP_NAME} shudown now."
            func_jenkins_stop
            exit 0;
        elif [[ ${status} = "${ST_LIST[1]}" ]]; then 
            func_output ${PID}
            exit 0;
        fi
    elif [[ ${CMD} = "${CMD_LIST[2]}" ]]; then
            func_jenkins_log
            exit 0;
    else
        echo "[ WARN ] check parameter. -> ${CMD}"
    fi
}

func_dir_chk() {
    for DIR_NAME in "${DIR_LIST[@]}"; do
        if [[ ! -d $DIR_NAME ]]; then
            echo "[INFO] CREATE DIRECTORY : $DIR_NAME"
            mkdir -p $DIR_NAME
        fi
    done
    func_cmd_ctrl 2>&1 | tee "${LOG_DIR}"/${DATE}.log
}

func_user_chk() {
    if [[ ${UNAME} != ${JEN_USER} ]]; then
        echo "[INFO] ${APP_NAME} can be start ${JEN_USER} user only ."
        exit 1;
    else
        func_dir_chk
    fi
}

func_user_chk

 


젠킨스 파이프라인

더보기

(추가:20/08/13)

pipeline을 직접 사용해보고 느낀점에 대해 장/단점으로 간략하게 설명하겠다.

- 장점 : 설정 관리적인 측면에서 CI/CD간의 Config를 형상으로 관리할 수 있다는 장점이 있다.
          (설정한 사람만 알고 있다면, mig하게될 시점에 분석하는데 골치아프다.)
- 단점 : 빌드 속도적인 측면에서 추가적인 딜레이 시간이 발생할 수 있다.
          (유효성 체크 로직 및 task가 추가될 수록)

관리포인트가 많으면 많을수록 관리 포인트에서 발생되는 업무와 책임을 가져가는 것은 관리자가 될 것이다.
따라서, 관리포인트를 최소화 해야하며, 이를 최소화 시킬 수 있는 역활을 하는 것이 pipeline이라고 보면 되겠다.이로 인해 얻을 수 있는 이점을 이용하여 코드는 공통화하여 효율적인 업무가 가능하도록 하는 것이 메인이라 보면 되겠다.

보다 상세한 내용은 보기를 보며 설명하겠다.

# [AS-IS]

참조1. 부가적인 task를 위해 jenkins jobs내, 추가적으로 옵션을 부여.(상단에 svn/git repo 추가 설정有).

 

(참조1)을 보면, 별다른 설정이 없는 것으로 보일 것이다. 저자가 jenkins 내, 설정을 추가하는 것을 별로 좋아하지 않기 때문에 별다른 설정이 없기 때문이나, 현업에서 jenkins job을 열다보면 많은 설정이 되어있는 경우가 매우 많다.

이같은 경우 마이그레이션 또는 사이트의 업그레이드가 이루어질 시, 헬게이트가 열리게되며 이런 일을 사전에 방지 및 보다 깔끔하고 안전한 관리를 위해 jenkins의 설정의 간소화 및 관리도 중요하다.

 

# [TO-BE]

참조2. jenkins jobs 내, 단순히 svn/git repo 설정만 존재.

 

(참조2)를 보면, repo(svn|git)만 연동하는 깨끗한 설정을 확인 할 수 있을 것이다. 이는 부가적인 task에 대해 전부 Script Path의 [Jenkinsfile]로 설정을 부여하였기 때문이며, 형상에서 해당 설정을 관리할 수 있다는 의미이기도 하다. 상단에서 이야기한 부가적인 관리포인트의 최소화가 가능해진 것이다. (장점)

pipeline에 대해 처음 접하여 걱정할 필요는 없다. 이유는 jenkins에서 이를 고려하여 generator를 제공해주고 있기 때문이다. 상단의 (참조2)의 Pipeline Syntax를 따라가보면 (참조3)과 같은 화면을 볼 수 있을 것이다.

 

참조3. Pipeline Generator/ jenkins에 설치되어있는 plugins를 기반으로 각각의 pipeline의 기능을 구현할 수 있도록 가이드.

 

(참조3)을 보면 ①Generator와 Documents 열람이 가능하며, ②Jenkins내, Plugins기반으로 Pipeline 기능을 만들 수 있도록 지원해주며, ③설정을 완료 후 Button 클릭 시, 하단에 박스 내 Pipeline 구문이 생성된다.

 

참조4. Pipeline jenkins job을 생성하고 난 이후, 실행하였던 내역을 보면 Replay라는 기능확인가능.

 

(참조3)과 같이 Generator가 있다하더라도 처음부터 설정을 완벽하게 할 수는 없을 것이다. 형상에 최초로 Jenkinsfile을 첨부하여 계속해서 수정/commit/실행의 반복으로 revision번호가 올라가다보면 비효율적인 작업의 연장이될 수 있기 때문이다.(저자도 그랬으니 말이다.)

이를 보다 효율적으로 하는 기능이 jenkins에는 있다. (참조4)를 보면 Replay라는 기능이며 최초로 Jenkinsfile을 형상에 내포한 이후부터 추가적인 기능개발/오류발생시에 Replay를 통해 pipeline을 지속적인 수정/개발을 하면서 확인이 가능하다.

최종적으로 원하는 기능 개발이 완료된 시점에 Jenkinsfile에 취합하여 commit하면 된다는 것이다.

 

참조5. 형상 내, Jenkinsfile을 생성.

 

(참조5)를 보듯 별다른 Task가 많이 보이지 않을 것이다. 이유로는 Task가 많아질수록 지연이 발생하기 때문이다.(단점) 지연이 발생되는 부분은 (참조6)과 (참조7)을 보겠다.

 

참조6. [참조1-AS-IS] non-pipeline 형태의 빌드 소요시간(평균33초~36초)/task:빌드&배포&재기동(WAS).

 

참조7. [참조2-TO-BE] pipeline 형태의 빌드 소요시간(평균41초~45초)/task: 참조6 동일.

 

(참조6)과 (참조7)을 보면 알듯 pipeline과 non-pipeline 사이에 동일한 logic task임에도 빌드시간이 pipeline에서 약10초 정도 지연이 발생한 것을 알 수 있을 것이다. pipeline만 다른데 10초나 추가되었다면 task나 유효성체크가 들어감에 따라 더욱 지연이 발생한다는 것이다.(실제 타프로젝트에서 pipeline 빌드 5분이상소요)

공개된 git/github을 보다보면 몇백~몇천라인의 pipeline의 장문이 자주 보인다. 대체적으로 유효성체크 로직이 많이 들어가 있는데 불필요한 유효성체크는 되려 독이 되는 경우가 많다.

필요한 기능을 구현을 위해서 정말 필요하고 간결한 구문만 넣어준다면 좋은 CI/CD가 될 것이다.

 

참조8. google에서 많은예시가 확인가능.

 

결과만 말한다면 처음으로 pipeline을 접하고 많은 참고와 여러가지 테스트와 시행/실습을 통해 사용을 해보았으나, 가장 효율 적이었던 것은 역시 필요한 기능만 넣어주는 것이었다. 예시는 google에서 검색하면 바로바로 확인이 가능하다.(참조8)

 

#- 참조5(Jenkinsfile)
#!/usr/bin/env groovy
pipeline {
    agent any
    options {
        ansiColor('css')
    }

    // global env variables
    environment {
        mvnHome = tool 'mvn3.3.9'
		javaHome = tool 'openjdk-1.8.0_191'
		sourceEnv = 'dev'
        appName = '플랫폼명'
		option = 'pot_was'
		ansibleController = '/home/cicd/scripts/pot/ansible-ctrl'
    }

    stages {
        stage('Build') {
            steps {
                script {
                    // Run the maven build
                    withEnv(["MVN_HOME=$mvnHome", "JAVA_HOME=$javaHome", "ENV=$sourceEnv"]) {
                        if (isUnix()) {
                           sh '"$MVN_HOME/bin/mvn" -DskipTests clean package install -P $ENV'
                        } else {
                           bat(/"%MVN_HOME%\bin\mvn" -DskipTests clean package install -P %ENV%/)
                        }
                    }
    	        }
            }
        }

        stage('Deploy') {
            when {
              expression {
                currentBuild.result == null || currentBuild.result == 'SUCCESS' 
              }
            }
            steps {
                script {
	            sh 'sh ${ansibleController} ${sourceEnv} ${appName} ${option}'
		}
            }
        }
    }
}

 

 

#- ansible-ctrl
#!/bin/bash

#--------------------------------------------------------------------------
#-- Set Error if Variable is Not Set
#-- Set Exit if Error is Occured
#--------------------------------------------------------------------------

#set -o nounset
set -o errexit

#--------------------------------------------------------------------------
# // Check Parameter & Define Variable
#--------------------------------------------------------------------------

if [[ $# -lt 3 ]]; then
    if [[ $# != $1 && $# != $2 && $# != $3 ]]; then
        echo
        echo "[ ERROR ] NOT FOUND ENVIRONMENT & PLATFORM & CMD : ($1) ($2) ($3)"
        echo "[ INFO ] ex ) ./ansible-ctrl ( dev or prd ) ( 플랫폼1 or 플랫폼2 or 플랫폼3 or 플랫폼4 or 플랫폼5 or 플랫폼6 ) ( pot_web or pot_was or recover_web or recover_was or restart_was )"
        echo
        exit 1
    fi
fi

#- SET CMD ARGS
ARGS1=$1
ARGS2=$2
ARGS3=$3

#- BASE_PRAM
export ID=`whoami`
export BASE_DIR=`cat /etc/passwd|grep ${ID}|cut -f6 -d ':'`
export SW_DIR=${BASE_DIR}/appsw
export APP_DIR=${SW_DIR}/ansible
export INVEN_DIR=${APP_DIR}/inventory

#- USER_PARM
POT_ID="cicd"
ENV_LIST=("dev" "prd")
PLATFORM_LIST=("플랫폼1" "플랫폼2" "플랫폼3" "플랫폼4" "플랫폼5" "플랫폼6")
CMD_LIST=("pot_web" "pot_was" "recover_web" "recover_was" "restart_was")
BOOK_LIST=("dev_플랫폼.yml" "prj_플랫폼.yml" "prd_플랫폼.yml")

#---------------------------------------------------------------
# // Validate Parameter & Run Script
#---------------------------------------------------------------
source $APP_DIR/hacking/env-setup

func_main() {
    func_chk_task ${ID} ${POT_ID} ${ARGS1} ${ARGS2} ${ARGS3} ENV_LIST PLATFORM_LIST CMD_LIST
    FILE=$(func_define ${RST1} ${RST2} ENV_LIST PLATFORM_LIST BOOK_LIST)
    func_action ${RST3} CMD_LIST ${INVEN_DIR} ${FILE} ${RST2} PLATFORM_LIST
}

func_chk_task() {
    local VAL1=$1
    local VAL2=$2
    local VAL3=$3
    local VAL4=$4
    local VAL5=$5
    local VAL6=$6
    local VAL7=$7
    local VAL8=$8
    local TML1=$VAL6[@]
    local TML1=("${!TML1}")
    local TML2=$VAL7[@]
    local TML2=("${!TML2}")
    local TML3=$VAL8[@]
    local TML3=("${!TML3}")

    func_user_chk ${VAL1} ${VAL2}
    export RST1=$(func_parm_chk ${VAL3} ${VAL6})
    export RST2=$(func_parm_chk ${VAL4} ${VAL7})
    export RST3=$(func_parm_chk ${VAL5} ${VAL8})

    if [[ -z ${RST1} ]]; then
        echo
        echo "[ ERROR ] PLEASE CHECK YOUR ENVIRONMENT ARGS1-VAL3 : ( ${VAL3} )"
        echo "[ INFO ] ex ) ./ansible-ctrl ( ${TML1[0]} or ${TML1[1]} )"
        echo
        exit 1
    fi
    if [[ -z ${RST2} ]]; then
        echo
        echo "[ ERROR ] PLEASE CHECK YOUR PLATFORM ARGS2-VAL4 : ( ${VAL4} )"
        echo "[ INFO ] ex ) ./ansible-ctrl ( ${RST1} ) ( ${TML2[0]} || ${TML2[1]} || ${TML2[2]} || ${TML2[3]} || ${TML2[4]} ||  ${TML2[5]} )"
        echo
        exit 1
    fi
    if [[ -z ${RST3} ]]; then
        echo
        echo "[ ERROR ] PLEASE CHECK YOUR CMD ARGS3-VAL5 : ( ${VAL5} )"
        echo "[ INFO ] ex ) ./ansible-ctrl ( ${RST1} ) ( ${RST2} ) ( ${TML3[0]} || ${TML3[1]} || ${TML3[2]} || ${TML3[3]} || ${TML3[4]} )"
        echo
        exit 1
    fi
    if [[ ${RST1} == ${TML1[0]} ]]; then
        if [[ ${RST3} == ${TML3[2]} || ${RST3} == ${TML3[3]} || ${RST3} == ${TML3[4]} ]]; then
            echo
            echo "[ WARN ] ( ${RST1} ) ENVIROMENT CAN NOT SET ( ${TML3[2]} || ${TML3[3]} || ${TML3[4]} ). ONLY CAN SET ( ${TML1[1]} )."
            echo "[ INFO ] ex ) ./ansible-ctrl ( ${TML1[1]} ) ( ${RST2} ) ( ${TML3[2]} || ${TML3[3]} || ${TML3[4]} )"
            echo
            exit 1
        fi
    elif [[ ${RST1} == ${TML1[1]} ]]; then
        if [[ ${RST2} == ${TML2[4]} || ${RST2} == ${TML2[5]} ]]; then
            echo
            echo "[ WARN ] ( ${RST1} ) ENVIROMENT CAN NOT SET ( ${TML2[4]} || ${TML2[5]} ). ONLY CAN SET ( ${TML1[0]} )."
            echo "[ INFO ] ex ) ./ansible-ctrl ( ${TML1[0]} ) ( ${TML2[4]} || ${TML2[5]} ) ( ${RST3} )"
            echo
            exit 1
        fi
    fi
}


func_user_chk() {
    local VAL1=$1
    local VAL2=$2
    if [[ ${VAL1} != ${VAL2} ]]; then
        echo "[ INFO ] CAN BE START (${VAL2}) USER ONLY."
        exit 1;
    fi
}

func_parm_chk() {
    local VAL1=$1
    local VAL2=$2[@]
    local VAL2=("${!VAL2}")
    for VALID in ${VAL2[@]} ;do
        if [[ ${VAL1} == ${VALID%/*} ]]; then
            echo "${VAL1}"
        else
            continue
        fi
    done
}

func_define() {
    local VAL1=$1
    local VAL2=$2
    local VAL3=$3[@]
    local VAL3=("${!VAL3}")
    local VAL4=$4[@]
    local VAL4=("${!VAL4}")
    local VAL5=$5[@]
    local VAL5=("${!VAL5}")

    if [[ ${VAL1} == ${VAL3[0]} ]];then
        if [[ ${VAL2} == ${VAL4[0]} || ${VAL2} == ${VAL4[1]} || ${VAL2} == ${VAL4[2]} || ${VAL2} == ${VAL4[3]} ]]; then
            echo "${VAL5[0]}"
        else
            echo "${VAL5[1]}"
        fi
    else
        echo "${VAL5[2]}"
    fi
}

func_action() {
    local VAL1=$1
    local VAL2=$2[@]
    local VAL2=("${!VAL2}")
    local VAL3=$3
    local VAL4=$4
    local VAL5=$5
    local VAL6=$6[@]
    local VAL6=("${!VAL6}")
    if [[ ${VAL5} == ${VAL6[3]} ]]; then
        echo "[ INFO ] ansible-playbook ${VAL3}/${VAL4} --tags=${VAL5}"
        ansible-playbook ${VAL3}/${VAL4} --tags=${VAL5}
    else
        if [[ ${VAL1} == ${VAL2[0]} ]]; then
            echo "[ INFO ] ansible-playbook ${VAL3}/${VAL4} --tags=${VAL5}_web"
            ansible-playbook ${VAL3}/${VAL4} --tags=${VAL5}_web
        elif [[ ${VAL1} == ${VAL2[1]} ]]; then
            echo "[ INFO ] ansible-playbook ${VAL3}/${VAL4} --tags=${VAL5}_was"
            ansible-playbook ${VAL3}/${VAL4} --tags=${VAL5}_was
        elif [[ ${VAL1} == ${VAL2[2]} ]]; then
            echo "[ INFO ] ansible-playbook ${VAL3}/${VAL4} --tags=${VAL5}_web_recover"
            ansible-playbook ${VAL3}/${VAL4} --tags=${VAL5}_web_recover
        elif [[ ${VAL1} == ${VAL2[3]} ]]; then
            echo "[ INFO ] ansible-playbook ${VAL3}/${VAL4} --tags=${VAL5}_was_recover"
            ansible-playbook ${VAL3}/${VAL4} --tags=${VAL5}_was_recover
        elif [[ ${VAL1} == ${VAL2[4]} ]]; then
            echo "[ INFO ] ansible-playbook ${VAL3}/${VAL4} --tags=${VAL5}_was_restart"
            ansible-playbook ${VAL3}/${VAL4} --tags=${VAL5}_was_restart
        fi
    fi
}

func_main

 

ps. ansible에 대해서는 다른장에서 기재하도록하겠다. 

 

 


젠킨스 오류/해결

더보기

(추가:19/11/22)

1. 오류내용 : 경고 [http-nio-8080-exec-1] org.apache.catalina.webresources.Cache.getResource [자원명]에 위치한 리소스를 웹 애플리케이션 [/jenkins]을(를) 위한 캐시에 추가할 수 없습니다. 왜냐하면 만료된 캐시 엔트리들을 없애버린 이후에도 여유 공간이 충분하지 않기 때문입니다. 캐시의 최대 크기를 증가시키는 것을 고려해 보십시오.

- 해결방안 : apache-tomcat-9.0.26/conf/context.xml 내, cache관련 설정추가

- 내용 : <Resources cachingAllowed="true" cacheMaxSize="100000" />

 

경고 메세지로 인한, tomcat기동시 로그메세지 대량누적 해결

 

#- 맺음말

오케스트레이션의 중심에 있는 만큼 여러가지 기능을 내포하고 있어 언제나 바쁘게 돌아가고 있다. 

그런 만큼, 우리를 도와주시는 집사님을 너무 구박하지 말도록하자.

 

#- References

공식홈페이지

모듈다운로드

모듈검색

(docker)docker-ce-install

(docker)docker-setting

(hub)jenkins_buleocean

(jenkins)pipeline

(jenkins)getting-started-buleocean

(jenkins)pipeline_example

 

'인프라 > MW' 카테고리의 다른 글

[GitLab/Linux or Unix] password reset (Root/Admin)  (0) 2019.12.20
(Keystore)Self-Signed-Cert-Trust-Manager(InstallCert.java)  (0) 2019.10.16
SVN(Subversion)  (0) 2019.09.26
Nginx  (0) 2019.09.26
Apache  (0) 2019.09.26