728x90

Jenkins

위키백과에서 발췌한 내용에 따른다.
젠킨스(Jenkins)는 소프트웨어 개발 시 지속적 통합(continuous integration) 서비스를 제공하는 툴이다. 다수의 개발자들이 하나의 프로그램을 개발할 때 버전 충돌을 방지하기 위해 각자 작업한 내용을 공유 영역에 있는 Git등의 저장소에 빈번히 업로드함으로써 지속적 통합이 가능하도록 해 준다. MIT 라이선스를 따른다.

발생 시점

현재의 회사에서 배포를 젠킨스를 이용하여 배포를 진행한다.
신규 기능개발과 레거시를 청산하는 작업을 주로 해왔었어서 이쪽을 고치는게 우선은 아니었다.
그래서 모르고 있었던 것일수 있다.
에러 상황을 확인해보자

에러 상세

[Pipeline] End of Pipeline
java.lang.InterruptedException
    at java.base/java.lang.Object.wait(Native Method)
    at java.base/java.lang.Thread.join(Thread.java:1300)
    at java.base/java.lang.Thread.join(Thread.java:1375)
    at java.base/jdk.internal.reflect.GeneratedMethodAccessor774.invoke(Unknown Source)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93)
    at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1213)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1022)
    at org.codehaus.groovy.runtime.InvokerHelper.invokePojoMethod(InvokerHelper.java:913)
    at org.codehaus.groovy.runtime.InvokerHelper.invokeMethod(InvokerHelper.java:904)
    at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethodN(ScriptBytecodeAdapter.java:168)
    at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethodNSafe(ScriptBytecodeAdapter.java:176)
    at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethodNSpreadSafe(ScriptBytecodeAdapter.java:183)
    at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethod0SpreadSafe(ScriptBytecodeAdapter.java:198)
    at org.hidetake.groovy.ssh.interaction.Interactions.waitForEndOfStream(Interactions.groovy:97)
    at org.hidetake.groovy.ssh.interaction.Interactions$waitForEndOfStream$2.call(Unknown Source)
    at org.hidetake.groovy.ssh.operation.Command.execute(Command.groovy:83)
    at org.hidetake.groovy.ssh.operation.Operation$execute$0.call(Unknown Source)
    at org.hidetake.groovy.ssh.session.execution.Command$Helper.execute(Command.groovy:50)
    at jdk.internal.reflect.GeneratedMethodAccessor951.invoke(Unknown Source)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93)
    at org.codehaus.groovy.runtime.callsite.StaticMetaMethodSite$StaticMetaMethodSiteNoUnwrapNoCoerce.invoke(StaticMetaMethodSite.java:151)
    at org.codehaus.groovy.runtime.callsite.StaticMetaMethodSite.call(StaticMetaMethodSite.java:91)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:141)
    at org.hidetake.groovy.ssh.session.execution.Command$Trait$Helper.execute(Command.groovy:30)
    at org.hidetake.groovy.ssh.session.execution.Command$Trait$Helper$execute$0.call(Unknown Source)
    at org.hidetake.groovy.ssh.session.SessionHandler.execute(SessionHandler.groovy)
    at jdk.internal.reflect.GeneratedMethodAccessor949.invoke(Unknown Source)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite$PogoCachedMethodSite.invoke(PogoMetaMethodSite.java:169)
    at org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite.call(PogoMetaMethodSite.java:71)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:133)
    at org.hidetake.groovy.ssh.session.execution.Command$Trait$Helper.execute(Command.groovy)
    at org.hidetake.groovy.ssh.session.execution.Command$Trait$Helper$execute.call(Unknown Source)
    at org.hidetake.groovy.ssh.session.SessionHandler.execute(SessionHandler.groovy)
    at jdk.internal.reflect.GeneratedMethodAccessor948.invoke(Unknown Source)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93)
    at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)
    at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:384)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1022)
    at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.callCurrent(PogoMetaClassSite.java:69)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:166)
    at org.jenkinsci.plugins.sshsteps.SSHService$_executeCommand_closure3$_closure13.doCall(SSHService.groovy:182)
    at org.jenkinsci.plugins.sshsteps.SSHService$_executeCommand_closure3$_closure13.doCall(SSHService.groovy)
    at jdk.internal.reflect.GeneratedMethodAccessor947.invoke(Unknown Source)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93)
    at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)
    at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:294)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1022)
    at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSite.java:42)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48)
    at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSite.java:57)
    at org.hidetake.groovy.ssh.util.Utility.callWithDelegate(Utility.groovy:17)
    at jdk.internal.reflect.GeneratedMethodAccessor427.invoke(Unknown Source)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93)
    at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)
    at org.codehaus.groovy.runtime.callsite.StaticMetaMethodSite.invoke(StaticMetaMethodSite.java:46)
    at org.codehaus.groovy.runtime.callsite.StaticMetaMethodSite.callStatic(StaticMetaMethodSite.java:102)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callStatic(AbstractCallSite.java:214)
    at org.hidetake.groovy.ssh.session.SessionTask.wetRun(SessionTask.groovy:64)
    at jdk.internal.reflect.GeneratedMethodAccessor6430.invoke(Unknown Source)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite$PogoCachedMethodSiteNoUnwrapNoCoerce.invoke(PogoMetaMethodSite.java:210)
    at org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite.callCurrent(PogoMetaMethodSite.java:59)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:158)
    at org.hidetake.groovy.ssh.session.SessionTask.call(SessionTask.groovy:48)
    at java_util_concurrent_Callable$call.call(Unknown Source)
    at org.hidetake.groovy.ssh.core.Service.run(Service.groovy:81)
    at org.hidetake.groovy.ssh.core.Service$run$1.call(Unknown Source)
    at org.jenkinsci.plugins.sshsteps.SSHService.executeCommand(SSHService.groovy:177)
    at org.jenkinsci.plugins.sshsteps.steps.CommandStep$Execution$CommandCallable.execute(CommandStep.java:84)
    at org.jenkinsci.plugins.sshsteps.util.SSHMasterToSlaveCallable.call(SSHMasterToSlaveCallable.java:32)
    at hudson.remoting.LocalChannel.call(LocalChannel.java:46)
    at org.jenkinsci.plugins.sshsteps.steps.CommandStep$Execution.run(CommandStep.java:72)
    at org.jenkinsci.plugins.sshsteps.util.SSHStepExecution.lambda$start$0(SSHStepExecution.java:84)
    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at java.base/java.lang.Thread.run(Thread.java:829)
Finished: ABORTED

정확히 이 에러 윗부분까지는 클라이언트의 요청을 받아서 처리해주고 있다가
한방에 서버가 다운되어버렸다.
젠킨스에서의 배포 스크립트에도 문제가 있었다.
아래의 쉘 스크립트를 젠킨스가 도커 컨테이너 내부에서 실행해주게 만들었다.

#!/bin/bash
export JAVA_TOOL_OPTIONS="-Dfile.encoding='UTF8' -Duser.timezone=Asia/Seoul"
kill -9 $(lsof -t -i:8080)
cd /root/server/
git pull
git checkout $1
git pull origin $1
kill -9 $(lsof -t -i:8080)
mvn clean package -P dev

java -jar -Dspring.profiles.active=dev target/server-0.0.1-SNAPSHOT.jar

이러한 구성으로 된 쉘 스크립트를 통해 실행을 진행했기 때문에
jar를 즉각 실행하게 만들어서 로그가 그대로 젠킨스에 전부 찍히고,

왜? 🤔

그야 당연할것인데, 로그를 실시간으로 젠킨스가 배포 과정을 찍을텐데,

java -jar 명령어를 백그라운드로 실행시켜주지를 않았다.

젠킨스의 배포는 항상 finished상태가 나오질 않는 상태였다.

항상 이상태였다 ㅋㅋㅋ

이미지

gif 처음만들어봤는데 재밌네..
이부분에서 로그가 과다하게 많이 쌓이게 되어 에러를 내뱉고
was가 죽어버린 상태가 되어버렸다. (비정상적 셧다운)

해결

JENKINS-45150 large console logging can take Jenkins down or affecting performance - Jenkins Jira
검색을 진행해보니 위와같은 내용들도 얻을 수 있었다.
그래서 해결과정의 순서를 생각한 방식은 다음과 같다.

  1. 젠킨스는 배포를 끝내서
    이러한 화면을 만들어주어야 한다.

  2. 스프링 애플리케이션의 배포 스크립트를 바꿔주어야 한다. (java -jar를 백그라운드로)

이렇게 하면 되겠다!

그래서 전부 바꿔주게 된다.

#!/bin/bash
export JAVA_TOOL_OPTIONS="-Dfile.encoding='UTF8' -Duser.timezone=Asia/Seoul"
kill -15 $(lsof -t -i:8080)
cd /root/server/
git pull
git checkout $1
git pull origin $1
kill -15 $(lsof -t -i:8080)
mvn clean package -P dev

nohup java -jar -Dspring.profiles.active=dev target/server-0.0.1-SNAPSHOT.jar > ~/app.log 2>&1 &

echo "Deploy Success"

nohup을 이용한 중단없이 실행해주고 젠킨스는 밖으로 빠져나와야 했기 때문에
이 명령어를 선택하고 실행해주었다.

그와 동시에 간단하게 라이브로 볼수있게끔 기본적으로 생성되는 nohup.out을 혹시몰라 만들어둔채로 마무리를 해놓았다.

그리고 kill명령어를 15로 바꾸었는데,
9는 강제종료기 때문에 진행중이던 작업을 즉시 종료하고 데이터도 저장하지 않는다.
15는 자신이 하던 작업을 모두 안전하게 종료하는 절차를 밟는다.
메모리상에 있는 데이터와 각종 설정/환경 파일을 안전하게 저장한 후 프로세스를 종료한다.
15로 한다고 한들, 종료 명령어를 주게되면, 어떤 클라이언트가 요청을 보내서 작업중인 데이터도 끊어질 것이다.
그래서 spring에서 제공하는 graceful shutdown을 적용하고 kill -15를 같이 붙여주었다.
graceful shutdown은 지금 포스팅에서 다루지 않겠다.
이렇게 해서 젠킨스의 로그 과다 적재로 서버가 죽는 현상을 제거시키게 되었다.

정리

이렇게 되어 젠킨스에 기존에 (내가 건드리지 않은) 잘못되게 설정되어 있던 것을 고치게 되니
괜찮다.
그러면서 동시에 툴도 툴마다의 각자의 할일이 있는 것인데,
CI/CD를 위한 툴에서 로그 모니터링까지 하고 있었으니 과다적재로 에러를 뱉는다는 것은
어찌보면 당연한 것이었을 수 있다고 나는 생각한다.🔥

728x90

'디버깅' 카테고리의 다른 글

FeignClient Logging level 디버깅  (0) 2022.12.17
@Async 사용시 에러 해결  (0) 2022.11.04
AWS SNS 토큰 에러  (0) 2022.08.10
YAML 파일을 읽어보자  (0) 2022.08.09
728x90

Docker란?

  • 컨테이너 기술을 지원하는 다양한 프로젝트중 하나
  • 컨테이너 기술의 사실상 표준
  • 다양한 운영체제에서 사용이 가능하다.(Linux, Mac OS, Windows)
  • 애플리케이션에 국한되지 않고 의존성 및 파일 시스템까지 패키징하여 빌드, 배포, 실행을 단순화
  • Linux의 NameSpace와 cgroups 커널 기능을 사용하여 가상화

도커는 다양한 클라우드 서비스 모델과 같이 사용가능

  • 이미지 : 필요한 프로그램과 라이브러리, 소스를 설치한 뒤 만든 하나의 파일
  • 컨테이너 : 이미지를 격리하여 독립된 공간에서 실행한 가상 환경

컨테이너가 해결한다!

  • 동일 시스템에서 실행하는 소프트웨어의 컴포넌트가 충돌하거나 다양한 종속성을 갖고 있음
  • 컨테이너는 가상머신을 사용해 각 마이크로 서비스를 격리하는 기술
  • 컨테이너는 가상머신처럼 하드웨어를 전부 구현하지 않기 때문에 매우 빠른 실행이 가능
  • 프로세스의 문제가 발생할 경우 컨테이너 전체를 조정해야 하기 때문에 컨테이너에 하나의 프로세스를 실행하도록 하는것이 좋다.

컨테이너, 서비스, 서버 가 많아질수록 비용이 절감되는 효과를 볼 수 있다❗

컨테이너를 격리하는 기술

리눅스 네임스페이스 : 각 프로세스가 파일 시스템 마운트, 네트워크, 유저, 호스트네임 등에 대해 시스템에 독립뷰 제공

리눅스 컨트롤 그룹 : 프로세스로 소비할 수 있는 리소스 양(CPU, 메모리, I/O, 네트워크 대역 등)을 제한

도커의 한계

서비스가 커지면 커질수록 관리해야 하는 컨테이너의 양이 급격히 증가
도커를 사용하여 관리를 한다고 하더라도 쉽지 않은 형태
배포 및 컨테이너 batch전략
scale-in, scale-out이 어려움

Docker는 OS의 자원을 이용하기 때문에 기본적으로 Root 사용자에서 명령어를 사용해야 한다.
Docker Docs 바로가기

설치

sudo -i # root 접속
# 비밀번호 있으면 입력 없으면 그대로
# 명령어 치는 곳 $에서 # 으로 변경 확인
apt install docker.io # 도커 설치

search(image 검색)

docker search !@%!@

  • Docker 허브로부터 사용가능한 이미지를 찾는 명령어
  • Docker는 Docker Hub를 통해 GitHub처럼 사용자들간의 이미지 공유를 할 수 있는 환경이 구축되어 있다.
  • 공식이미지는 / 앞에 사용자 이름이 붙지 않는것이다.

Pull(image 다운로드)

docker pull tomcat:latest
Docker 허브로부터 이미지 다운로드

images(image 목록 보기)

# docker images
현재 PC에 다운 받아져있는 image들을 출력하는 명령어

run

컨테이너 생성과 동시에 컨테이너로 접속하는 명령어

# docker run "REPOSITORY"
(docker run <옵션><이미지이름 or 이미지ID><실행할 파일>)

  • 단순히 image안의 파일을 실행할 목적으로 생성된 것이기 때문에 메인으로 실행되는 파일이 종료되면 컨테이너도 같이 종료된다. 따라서 계속해서 컨테이너를 유지하고 싶다면 -d 옵션을 이용해야 한다.

  • 옵션

    • -i : interactive
      • 사용자가 입출력을 할 수 있는 상태로 한다.
    • -t : 가상 터미널 환경을 에뮬레이션 하겠다는 설정
    • -d : 컨테이너를 일반 프로세스가 아닌 데몬프로세스 형태로 실행하여 프로세스가 끝나도 유지되도록 한다.

create

컨테이너 생성 명령어
# docker create <옵션> <포트번호> <이름> <이미지명>
docker create -d -p 80:80 --name nx nginx
이런식으로 명령어 사용이 가능하다
컨테이너 계층을 생성하고 명령을 실행하게끔만 만드는 단계이다.

옵션

  • -p : 컨테이너의 포트를 호스트에 게시함
  • --name : 컨테이너에 이름 할당

start

docker start [OPTIONS] CONTAINER [CONTAINER...]
create에서 할당한 이름값으로 시작할 수 있고 containerId로도 실행이 가능하다.

stop

docker stop id or container
실행중인 container를 중지시킨다.
-t 옵션으로 일정시간 지난후에 중지 시킬수도 있다.

ps

컨테이너 목록보기 명령어
docker ps로 볼수있다.
기본값은 실행되고있는 컨테이너만 표시한다.
그래서 중지된 컨테이너도 보려면 docker ps -a 를 사용하면 된다.

옵션

  • --all, -a
    • 모든 컨테이너 표시(기본값은 실행만 표시)
  • --quiet, -q
    • 컨테이너 ID만 표시

remove

제거 명령은 container를 중지 시킨 후에 가능하다

docker rm containerId or name

중지시킨후에 container 제거 명령을 할 수있다.

rmi는 이미지를 지우는 것으로 설치했던것을 삭제하는 명령이다.

docker rmi nginx 이런식으로 받았던 이미지 이름을 rmi 뒤에 넣어준다.

컨테이너 내부 shell 실행

sudo docker exec -it tc /bin/bash

컨테이너 로그 확인

sudo docker logs tc # stdout, stderr

728x90

'클라우드' 카테고리의 다른 글

OpenSSH  (0) 2022.08.09
Docker compose  (0) 2022.08.07

+ Recent posts