젠킨스에 대해서 간략하게 소개를 하도록하겠다.
젠킨스는 CI/CD를 관리해주는 우리의 집사님이다.
평소에는 성실해서 언제나 웃어주시지만,
한번 열받으면 무서운 분이니 조심하자.
젠킨스에 대한 상세한 내용은 젠킨스를 참고하면 되겠다.
이야기를 진행하여, 초기 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. 브라우저 확인
![](https://blog.kakaocdn.net/dn/bT1l9D/btqyWLCShLr/r4ZkobPlrOwzlNnKSN61NK/img.jpg)
![](https://blog.kakaocdn.net/dn/crrYXH/btqyV52Sv2c/K3MufWOrB2alW2LK5ytd50/img.jpg)
![](https://blog.kakaocdn.net/dn/debLVQ/btqyYpS8G9v/DcRUYJ0YDdRGAmkUuIHEu1/img.jpg)
![](https://blog.kakaocdn.net/dn/d51gxr/btqyWVytlfD/PSHYiqeQLwuCj4FrVEXSOK/img.jpg)
![](https://blog.kakaocdn.net/dn/cl5ZwE/btqyU8euFcC/GSSxKqPgOKNXUEdGKpzOMk/img.jpg)
어드민 계정 패스워드 분실/초기화
※ 패스워드 정책 적용
![](https://blog.kakaocdn.net/dn/bG9O2u/btqyYo71jQX/yDQyD6W4E8WKZBYGwNNAak/img.jpg)
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
![](https://blog.kakaocdn.net/dn/Nkp6a/btqyYoNzG64/t1ObSOaAWxdDTuvLKIk2E1/img.png)
<useSecurity>true</useSecurity>
<authorizationStrategy class="hudson.security.FullControlOnceLoggedInAuthorizationStrategy">
<denyAnonymousReadAccess>true</denyAnonymousReadAccess>
</authorizationStrategy>
<securityRealm class="hudson.security.HudsonPrivateSecurityRealm">
<disableSignup>true</disableSignup>
<enableCaptcha>false</enableCaptcha>
</securityRealm>
![](https://blog.kakaocdn.net/dn/cIJwV6/btqyWLbT3Je/IEpf9UhKKBGohuKEl5Z2z1/img.png)
2. tomcat 재기동
cd ${BASE_DIR}/appsw/apache-tomcat-9.0.26/bin
./shutdown.sh
./startup.sh
![](https://blog.kakaocdn.net/dn/vEYLE/btqyWnoN67d/ZkbMAD943fgjLUR6f4q9JK/img.png)
![](https://blog.kakaocdn.net/dn/8G2PN/btqyU8FCwmQ/YXrOYb1wUQnRNG0kZ5VAF0/img.jpg)
3. 신규 정책 추가(어드민권한계정 추가생성)
![](https://blog.kakaocdn.net/dn/bfwp0t/btqyWKqFNkV/gZBG8YMdReMIunGAUhnNw1/img.jpg)
![](https://blog.kakaocdn.net/dn/7RQpO/btqyY2QYD1m/hSldPwmeFHohsKaYcsG8vk/img.jpg)
4. 설정 취합 & 재기동
vi ${BASE_DIR}/jenkins_home/config.xml
![](https://blog.kakaocdn.net/dn/bGUKjN/btqyY15Bvqp/mHpHFaTsYXAIpFAsHyFMX1/img.jpg)
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 재시작.
![](https://blog.kakaocdn.net/dn/ey8QQg/btqyXqkNg3W/bDXYvPpyA8BdUZyS0jizW0/img.jpg)
![](https://blog.kakaocdn.net/dn/bhOzbo/btqyYqraHfM/JwVoJcsKK5OnfAvazz5g40/img.jpg)
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
![](https://blog.kakaocdn.net/dn/qI9jF/btqzIgpodAX/34QNGgIbiAGSkqOWVk1kX1/img.jpg)
sudo vi /etc/sudoers
#- 계정권한조율은 References(Sudoers)를 참고
설정이유 : * docker 계정으로 package 설치를 편하게 하기위해
* 설정 하고 싶지 않을 시, root권한으로 작업 진행
docker ALL=(ALL) NOPASSWD:ALL
![](https://blog.kakaocdn.net/dn/bt8YTV/btqzK1qysXF/GojPGOZhxA7JK2XssXzJsk/img.jpg)
※ 다른계정으로 추가시, 하단참조
#- 계정 생성단계 진행불필요(그룹만 생성)
sudo groupadd docker
#- 사용하고자 하는 계정에서, 대상 docker그룹맵핑
sudo usermod -aG docker $USER
#- 그룹권한 현행화
sudo newgrp docker
2. Docker설치 전, 사전작업(OS Package Update)
sudo yum update -y
![](https://blog.kakaocdn.net/dn/6sZx6/btqzLkpSyEC/Kr4NINJWKU6S53U9RKJpvK/img.jpg)
3. Docker 설치
sudo yum install docker -y
![](https://blog.kakaocdn.net/dn/debjOn/btqzKZM2l01/Di2KbNU4tknljhKrNHx1K0/img.jpg)
4. Docker 설정
#- chkconfig 목차 등록 시, systemd로 대상 서비스 FWD
sudo chkconfig docker on
-> systemctl enable docker.service
![](https://blog.kakaocdn.net/dn/czuZ4T/btqzKHlyWdp/yxfKUxhxktTkR6SKjCeFD1/img.jpg)
5. Docker 기동
#- Docker 기동 및 프로세스 확인
sudo systemctl start docker
ps -ef | grep docker
#- Docker 정상 실행 여부확인
sudo docker run hello-world
![](https://blog.kakaocdn.net/dn/dx7gTY/btqzK0kSxFc/g800Qbyj0J2ERIPyOjnNz1/img.jpg)
6. Docker 상태확인
sudo systemctl status docker
![](https://blog.kakaocdn.net/dn/dorag0/btqzIzPKqdW/zzlyh9387yku5LcXKuOweK/img.jpg)
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
![](https://blog.kakaocdn.net/dn/U33za/btqzJGAGyqI/xtm7i4m6KE0o97Dyl9yX90/img.jpg)
#- 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설정 작업을 진행 시, 오류 발생하니 둘중 하나만 사용한다.
![](https://blog.kakaocdn.net/dn/b2NFDH/btqzJb8MloL/V3VnuKyauD3NcKO02pPrMK/img.jpg)
/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
![](https://blog.kakaocdn.net/dn/bAcPWf/btqzIz3hTvb/GCm0fbvZXsNYBXbDDZOCJk/img.jpg)
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
![](https://blog.kakaocdn.net/dn/mSemv/btqzIAVvlN5/zxNGGXkUYHJqnuTkaRuezk/img.jpg)
![](https://blog.kakaocdn.net/dn/b7IlJ3/btqzIAHXpTs/KLp4I2JrlZQIvOGlEHdQ1K/img.jpg)
docker logs jenkins
![](https://blog.kakaocdn.net/dn/C2nnm/btqzJF2SQSc/cmM6kBaRWZFr9ylBHr13vK/img.jpg)
10. Jenkins 브라우저 확인
![](https://blog.kakaocdn.net/dn/qDggl/btqzJEQrko3/aaVcqKYikVDFn9pJFl3hO0/img.jpg)
![](https://blog.kakaocdn.net/dn/qbOzl/btqzK0rIM8r/XskGcD355TJTuMxOv5arkK/img.png)
![](https://blog.kakaocdn.net/dn/mRM0h/btqzLj5ECzS/ka5bP7jrb9zY4q7JGSpnt1/img.jpg)
![](https://blog.kakaocdn.net/dn/FPjDW/btqzIAVx9y5/qO0nY2ic4G6keqhPTxmKNk/img.jpg)
![](https://blog.kakaocdn.net/dn/pOmwj/btqzJEv59NN/IM7BVNXHwWWGb2ut2dAh4K/img.jpg)
http://${my.server.ip}:8080
![](https://blog.kakaocdn.net/dn/b40sXG/btqzK0FeBiw/l2XvAkK72k39aeyYKXFbwK/img.jpg)
![](https://blog.kakaocdn.net/dn/dkl36u/btqzKejGxv9/qUhvYU8yLGsBtnXzvghM2k/img.jpg)
![](https://blog.kakaocdn.net/dn/dtm800/btqzIfYmZBE/NkD8zQKvMapyFwb1Wuqzv0/img.jpg)
![](https://blog.kakaocdn.net/dn/c2sIVE/btqzIhhB0Wa/afUf48kROkXGBo2qRCKO70/img.jpg)
※추가사항-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]
![](https://blog.kakaocdn.net/dn/PqkjU/btqGvcAzphS/hJiIPK8IWUB62qRYhJbb90/img.jpg)
(참조1)을 보면, 별다른 설정이 없는 것으로 보일 것이다. 저자가 jenkins 내, 설정을 추가하는 것을 별로 좋아하지 않기 때문에 별다른 설정이 없기 때문이나, 현업에서 jenkins job을 열다보면 많은 설정이 되어있는 경우가 매우 많다.
이같은 경우 마이그레이션 또는 사이트의 업그레이드가 이루어질 시, 헬게이트가 열리게되며 이런 일을 사전에 방지 및 보다 깔끔하고 안전한 관리를 위해 jenkins의 설정의 간소화 및 관리도 중요하다.
# [TO-BE]
![](https://blog.kakaocdn.net/dn/Y541M/btqGzXIUA0y/VxsCYd4E5zeBvjSmXSXpD0/img.jpg)
(참조2)를 보면, repo(svn|git)만 연동하는 깨끗한 설정을 확인 할 수 있을 것이다. 이는 부가적인 task에 대해 전부 Script Path의 [Jenkinsfile]로 설정을 부여하였기 때문이며, 형상에서 해당 설정을 관리할 수 있다는 의미이기도 하다. 상단에서 이야기한 부가적인 관리포인트의 최소화가 가능해진 것이다. (장점)
pipeline에 대해 처음 접하여 걱정할 필요는 없다. 이유는 jenkins에서 이를 고려하여 generator를 제공해주고 있기 때문이다. 상단의 (참조2)의 Pipeline Syntax를 따라가보면 (참조3)과 같은 화면을 볼 수 있을 것이다.
![](https://blog.kakaocdn.net/dn/cvf0Y1/btqGvdMXWMr/JBcrveHrPjsrd0i1qpbMf0/img.jpg)
(참조3)을 보면 ①Generator와 Documents 열람이 가능하며, ②Jenkins내, Plugins기반으로 Pipeline 기능을 만들 수 있도록 지원해주며, ③설정을 완료 후 Button 클릭 시, 하단에 박스 내 Pipeline 구문이 생성된다.
![](https://blog.kakaocdn.net/dn/bBU97J/btqGzX3emnd/tnW9jtqWzo9YtkX8pmYhb0/img.jpg)
(참조3)과 같이 Generator가 있다하더라도 처음부터 설정을 완벽하게 할 수는 없을 것이다. 형상에 최초로 Jenkinsfile을 첨부하여 계속해서 수정/commit/실행의 반복으로 revision번호가 올라가다보면 비효율적인 작업의 연장이될 수 있기 때문이다.(저자도 그랬으니 말이다.)
이를 보다 효율적으로 하는 기능이 jenkins에는 있다. (참조4)를 보면 Replay라는 기능이며 최초로 Jenkinsfile을 형상에 내포한 이후부터 추가적인 기능개발/오류발생시에 Replay를 통해 pipeline을 지속적인 수정/개발을 하면서 확인이 가능하다.
최종적으로 원하는 기능 개발이 완료된 시점에 Jenkinsfile에 취합하여 commit하면 된다는 것이다.
![](https://blog.kakaocdn.net/dn/oYX6Y/btqGxmipf4q/lrqf84LwwymHKCDLjQHEP1/img.jpg)
(참조5)를 보듯 별다른 Task가 많이 보이지 않을 것이다. 이유로는 Task가 많아질수록 지연이 발생하기 때문이다.(단점) 지연이 발생되는 부분은 (참조6)과 (참조7)을 보겠다.
![](https://blog.kakaocdn.net/dn/bScINh/btqGxOMwfyv/7SgssjSmseE0J40tLNJ0Zk/img.jpg)
![](https://blog.kakaocdn.net/dn/b5BtZG/btqGCdRAfxJ/aXpTvTecFAm4EYEF7xlKO0/img.jpg)
(참조6)과 (참조7)을 보면 알듯 pipeline과 non-pipeline 사이에 동일한 logic task임에도 빌드시간이 pipeline에서 약10초 정도 지연이 발생한 것을 알 수 있을 것이다. pipeline만 다른데 10초나 추가되었다면 task나 유효성체크가 들어감에 따라 더욱 지연이 발생한다는 것이다.(실제 타프로젝트에서 pipeline 빌드 5분이상소요)
공개된 git/github을 보다보면 몇백~몇천라인의 pipeline의 장문이 자주 보인다. 대체적으로 유효성체크 로직이 많이 들어가 있는데 불필요한 유효성체크는 되려 독이 되는 경우가 많다.
필요한 기능을 구현을 위해서 정말 필요하고 간결한 구문만 넣어준다면 좋은 CI/CD가 될 것이다.
![](https://blog.kakaocdn.net/dn/cW2RKK/btqGvZVgwjs/luWxPikzhihkUtTJEuOf0k/img.jpg)
결과만 말한다면 처음으로 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" />
![](https://blog.kakaocdn.net/dn/cHHnnd/btqzYa1sKrP/tvsLbtiUtypESfqF2cz6oK/img.jpg)
#- 맺음말
오케스트레이션의 중심에 있는 만큼 여러가지 기능을 내포하고 있어 언제나 바쁘게 돌아가고 있다.
그런 만큼, 우리를 도와주시는 집사님을 너무 구박하지 말도록하자.
#- References
(jenkins)getting-started-buleocean
'인프라 > 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 |