{"data":{"site":{"siteMetadata":{"siteUrl":"https://sat10am.dev","shortname":"sat10am"}},"post":{"id":"Post_3-markdown","title":"Jenkins x Docker Tomcat War 배포하기","view":0,"slug":"/posts/jenkins-docker","publishAt":"2018.10.22","tags":[{"name":"docker"},{"name":"jenkins"}],"childMarkdownRemark":{"html":"<p>요즘의 소프트웨어 개발은 어떻게 만들까도 많이 고민하지만 어떻게 운영하고 유지보수 할 것인가? 에 대한 고민을 많이 하는것 같다. 어플리케이션 구현보다는 운영에 초점이 맞춰지는 것이다.  소프트웨어 개발을 분석/개발/테스트/배포/운영 의 단계로 나눈다면 배포/운영을 뜻한다. 필자는 생산성에 있어서 굉장히 관심이 많은 편인데 개발을 하면서 지속적으로 느끼는점이 배포/운영에 대한 정확한 프로세스가 정확히 구축되어있지 않으면 추후에 많은 생산성 낭비를 가져올 수 있다는 것이다.</p>\n<p>Docker를 사용하기 전에는 Maven기반의 프로젝트를 배포할때 보통 Jenkins에서 SCP와 SSH를 통한 배포 혹은 <a href=\"https://plugins.jenkins.io/deploy\">Deploy to Container Plugins</a> , Maven 의 Tomcat 플러그인 등 여러가지 방법을 통해서 배포해왔지만 먼가 부족한 느낌이 들었다.\n이런방식들 모두 배포하는 서버환경에 의존적이거나 WAS  Container에 의존적이기 때문이다. Docker는 DevOps의 도구로 자주쓰이는 도구인 만큼 많은 의존성들을 제거할 수 있고 멱등성(idempotent)을 제공한다.</p>\n<p>이번 포스팅은 Maven기반의 프로젝트를 Dockerizing 후 Jenkins를 통해서 배포하는 Workflow 구축에 대해 소개하려한다.</p>\n<h2 id=\"deploy-flow\"><a href=\"#deploy-flow\" aria-label=\"deploy flow permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Deploy Flow</h2>\n<p><img src=\"https://sat10am-blog.s3.ap-northeast-2.amazonaws.com/bd5badf2371c484fba315d98fc4a2b35.png\" alt=\"text\"></p>\n<ol>\n<li>개발한 내용을 Git, Svn 에 반영한다.</li>\n<li>Jenkins Workspace로 Pull 을 받는다.</li>\n<li>Maven 혹은, Gradle과 같은 빌드 도구를 통해 Build를 한다.</li>\n<li>Docker Image 로 Dockerizing 한다.</li>\n<li>Private Registry에 Push 해준다.</li>\n<li>원격지에서  Image를 Pull 받은 후 Container를 배포한다. (Run)</li>\n</ol>\n<p>여기서 5번인 Private Registry는 구지 따로 구축하고 싶지않다면 SSH를 통해서 이미지를 빌드하거나 다른방법을 사용하여도 좋다.  이 글에선 Registry 를 이용한다.</p>\n<h3 id=\"jenkins-설치\"><a href=\"#jenkins-%EC%84%A4%EC%B9%98\" aria-label=\"jenkins 설치 permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Jenkins 설치</h3>\n<p>먼저 Jenkins를 설치해보자. 이번 주제는 Docker를 활용하는 만큼 Docker를 통해서 설치해볼 것이다.</p>\n<p>Docker를 이용한 설치는 정말 간단하다.</p>\n<div class=\"gatsby-highlight\" data-language=\"bash\"><pre class=\"language-bash\"><code class=\"language-bash\">docker pull jenkins/jenkins:latest\ndocker run -p 8080:8080 \\\n   \t  -e TZ<span class=\"token operator\">=</span><span class=\"token string\">'Asia/Seoul'</span>\n        --name jenkins \\\n        -v ./jenkins:/var/jenkins_home -d</code></pre></div>\n<p>간단하게 사용한다면 이정도만 가지고 실행시키면 된다.</p>\n<p>하지만 Jenkins를 Docker로 설치할 경우 Docker 내부에서 Pull &#x26; Build가 진행되기 때문에 배포는 모두 Container기준으로 원격지가 된다. 즉 Host OS에 배포하는것도 원격지처럼 배포해야 한다.</p>\n<p>SSH를 통해서 명령어를 실행시킬수도 있지만 Docker 에서는 TCP Socket을 지원한다.\n이 TCP Socket을 이용하면 환경변수 지정하는것으로 Remote 에 있는 Docker 명령이 가능해진다.</p>\n<p>간단한 예제를 통해서 이해해보자</p>\n<div class=\"gatsby-highlight\" data-language=\"bash\"><pre class=\"language-bash\"><code class=\"language-bash\"><span class=\"token shebang important\">#!/bin/bash</span>\n<span class=\"token function\">export</span> DOCKER_HOST<span class=\"token operator\">=</span>tcp://192.168.0.10:4243\ndocker <span class=\"token function\">ps</span></code></pre></div>\n<p>위 스크립트를 실행하게되면 해당 주소에 있는 docker 프로세스 정보를 보여준다.</p>\n<p>TCP Socket을  사용하기위해 준비해야할 것들이 있다.</p>\n<ul>\n<li>Jenkins Container 내부에 Docker가 설치 되어있어야 한다.</li>\n<li>배포할 원격지에 TCP Socket이 활성화 되어있어야 한다.\n<a href=\"https://success.docker.com/article/how-do-i-enable-the-remote-api-for-dockerd\">Docker - How do I enable the remote API for dockerd</a> 참고</li>\n</ul>\n<p>Jenkins 이미지에서 Docker 명령이 가능하도록 Image를 커스터마이징 한다.\n아래 Dockerfile을 참고하자</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\"># dind-jenkins\nFROM jenkinsci/jenkins:latest\n\nUSER root\nRUN apt-get update -qq\nRUN apt-get install -qqy apt-transport-https ca-certificates\nRUN apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D\nRUN echo deb https://apt.dockerproject.org/repo debian-jessie main &gt; /etc/apt/sources.list.d/docker.list\nRUN apt-get update -qq\nRUN apt-get install -qqy docker-engine\n\nUSER jenkins</code></pre></div>\n<p>이렇게 docker container 내부에 docker를 설치하는것을 Docker in Docker 라고 한다.</p>\n<p>위 파일로 dind-jenkins 이미지를 Build 한 후 필요한 옵션만 넣어서 Run 시킨다.\n초기에 패스워드를 넣는 창이 나오는데 Docker Container 에 들어가서 확인해야한다.</p>\n<div class=\"gatsby-highlight\" data-language=\"bash\"><pre class=\"language-bash\"><code class=\"language-bash\">docker <span class=\"token function\">exec</span> -it dind-jenkins /bin/bash\n<span class=\"token function\">cat</span> /var/lib/jenkins/secrets/initialAdminPassword</code></pre></div>\n<p>그 이후에는 플러그인을 설치하고 계정설정을 해주면 된다.</p>\n<p>Jenkins 설치가 완료됬으면 Pipeline Job을 생성한다.\n기호에 따라 Maven Job이나 FreeStyle Job을 생성할 수 있지만 최근엔 인프라 설정을 수동으로 설정하는 대신 코드로 관리하는 Infrastructure as Code방식이 많이 쓰이고 있다.</p>\n<p>프로젝트 루트에 Jenkins파일과 Dockerfile을 생성한다.\nJenkinsfile은 Jenkins Build 설정을 작성해야한다.</p>\n<div class=\"gatsby-highlight\" data-language=\"bash\"><pre class=\"language-bash\"><code class=\"language-bash\">def mvnHome\nnode <span class=\"token punctuation\">{</span>\n    try <span class=\"token punctuation\">{</span>\n        stage<span class=\"token punctuation\">(</span><span class=\"token string\">'Checkout'</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n            checkout scm\n            mvnHome <span class=\"token operator\">=</span> tool <span class=\"token string\">'M3'</span>\n        <span class=\"token punctuation\">}</span>\n        stage<span class=\"token punctuation\">(</span><span class=\"token string\">'Environment'</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n            sh <span class=\"token string\">'git --version'</span>\n            <span class=\"token keyword\">echo</span> <span class=\"token string\">\"Branch: <span class=\"token variable\">${env.BRANCH_NAME}</span>\"</span>\n            sh <span class=\"token string\">'docker -v'</span>\n            sh <span class=\"token string\">'printenv'</span>\n        <span class=\"token punctuation\">}</span>\n        stage<span class=\"token punctuation\">(</span><span class=\"token string\">'Push Image'</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n            sh <span class=\"token string\">\"'<span class=\"token variable\">${mvnHome}</span>/bin/mvn' clean install -P production\"</span>\n            sh<span class=\"token punctuation\">(</span><span class=\"token string\">'scripts/image.sh'</span><span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">}</span>\n        stage<span class=\"token punctuation\">(</span><span class=\"token string\">'Deploy'</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n            sh<span class=\"token punctuation\">(</span><span class=\"token string\">'scripts/deploy.sh'</span><span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">}</span>\n    <span class=\"token punctuation\">}</span> catch <span class=\"token punctuation\">(</span>err<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        throw err\n    <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p><code class=\"language-text\">image.sh</code>  에는 Private Registry에 Push하는 스크립트파일이다.\n<code class=\"language-text\">deploy.sh</code> 에는 Private Registry에서 Pull 받은 후 Docker Container 를 run 하는 스크립트 파일이다.</p>\n<p>상황에 맞게 골격에 맞춰서 스크립트를 작성해주면 된다.</p>\n<p>아래는 war파일을 Docker Image로 Dockerizing 할 수 있는 Dockerfile 이다.</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">FROM tomcat:8-jre8\nWORKDIR /usr/local/tomcat\nRUN rm -rf ./webapps/*\nCOPY ./target/*.war ./webapps/ROOT.war\nEXPOSE 8080\nCMD $CATALINA_HOME/bin/startup.sh &amp;&amp; tail -f $CATALINA_HOME/logs/catalina.out</code></pre></div>\n<p>tomcat은 알맞는 jre 버전과 톰캣 버전을  선택해서 Base Image 를 생성하면 된다.</p>\n<h2 id=\"마무리\"><a href=\"#%EB%A7%88%EB%AC%B4%EB%A6%AC\" aria-label=\"마무리 permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>마무리</h2>\n<p>Docker를 활용한 배포는 기존의 방식보다 서버에 대한 의존성에 많은 부분을 제거할 수 있다. Tomcat Manager를 통해서 배포할떈 Out of memory 혹은 jdk 버전의 문제 등 몇가지 이슈가 있었는데 많은부분을 해소해주었다. 다음엔 Docker Swarm을 통한 Container orchestration 관리에 대해 다뤄보려한다.</p>"},"author":{"username":"y0c","intro":"Web Developer & Student","email":"holnet1026@gmail.com","github":"https://github.com/y0c","profile":{"url":"/uploads/28f8f2ee1ab84c0b86610b60402bc4ee.jpeg"}},"thumbnail":{"childImageSharp":{"fluid":{"aspectRatio":1.5486725663716814,"src":"/static/cda23e84eeb1796a82bac99df7f34e8f/489f2/faff731c89f344c39a06be67c6bfe3f9.png","srcSet":"/static/cda23e84eeb1796a82bac99df7f34e8f/01d6e/faff731c89f344c39a06be67c6bfe3f9.png 175w,\n/static/cda23e84eeb1796a82bac99df7f34e8f/56996/faff731c89f344c39a06be67c6bfe3f9.png 350w,\n/static/cda23e84eeb1796a82bac99df7f34e8f/489f2/faff731c89f344c39a06be67c6bfe3f9.png 700w,\n/static/cda23e84eeb1796a82bac99df7f34e8f/d1d6e/faff731c89f344c39a06be67c6bfe3f9.png 1050w,\n/static/cda23e84eeb1796a82bac99df7f34e8f/de042/faff731c89f344c39a06be67c6bfe3f9.png 1400w,\n/static/cda23e84eeb1796a82bac99df7f34e8f/f8a62/faff731c89f344c39a06be67c6bfe3f9.png 1890w","sizes":"(max-width: 700px) 100vw, 700px"}}}},"previous":{"title":"You Don't Know JS: 비동기성 - 지금과 나중","slug":"/posts/javascript-asynchrony"},"next":{"title":"Docker Private Registry 구축하기","slug":"/posts/docker-private-registry"}},"pageContext":{"isCreatedByStatefulCreatePages":false,"id":"Post_3-markdown","hasPrevious":true,"hasNext":true,"previousId":"Post_5-markdown","nextId":"Post_2-markdown"}}