<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Jenkins on</title><link>https://taetaetae.github.io/tags/jenkins/</link><description>Recent content in Jenkins on</description><generator>Hugo</generator><language>en</language><lastBuildDate>Sun, 06 Dec 2020 20:19:47 +0900</lastBuildDate><atom:link href="https://taetaetae.github.io/tags/jenkins/index.xml" rel="self" type="application/rss+xml"/><item><title>Jenkins Job을 병렬로 실행해서 속도를 개선해보자. (by. Pipeline)</title><link>https://taetaetae.github.io/posts/jenkins-job-parallel-processing-by-pipeline/</link><pubDate>Sun, 06 Dec 2020 20:19:47 +0900</pubDate><guid>https://taetaetae.github.io/posts/jenkins-job-parallel-processing-by-pipeline/</guid><description>&lt;p>﻿　관리하는 URL이 &lt;code>200&lt;/code>응답을 주고 있는지 모니터링을 한다고 가정해보자. 다양한 방법이 생각나겠지만 가장 처음으로 떠오른 건 단연 &lt;code>Jenkins&lt;/code>. 간단하게 사용할 언어에 맞춰 &lt;code>Execute Script&lt;/code>를 작성하고 스케줄링을 걸어 놓으면 큰 수고 없이 모니터링을 구성할 수 있게 된다. 아래는 python script로 작성해 보았다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">requests&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">url&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;http://모니터링url&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">status_code&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">requests&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">status_code&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="n">status_code&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="mi">200&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s1">&amp;#39;응답 실패 :&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s1">, status : &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">status_code&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s1">&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="n">exit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>﻿　하지만 모니터링을 해야 하는 URL이 1개에서 여러 개로 늘어난다면 어떻게 될까? 단순하게 작성한 Script를 아래처럼 약간 수정하면 되긴 하지만 URL마다 응답속도가 다를 경우 순차적으로 실행하다 보니 실행 속도는 느릴 수밖에 없다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">requests&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">urls&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="s2">&amp;#34;http://모니터링url-1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="s2">&amp;#34;http://모니터링url-2&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="s2">&amp;#34;http://모니터링url-3&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="n">url&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">urls&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="n">status_code&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">requests&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">status_code&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="k">if&lt;/span> &lt;span class="n">status_code&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="mi">200&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s1">&amp;#39;응답 실패 :&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s1">, status : &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">status_code&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s1">&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="n">exit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>﻿
　이러한 경우, 빠른 속도를 보장하기 위해서는 병렬로 실행을 해야 한다는 건 누구나 다 알지만 그렇다고 Thread를 사용하기엔 벌써부터 덜컥 부담이 된다. 그렇다고 Job을 URL 개수만큼 늘리기에는 배보다 배꼽이 더 커버리고&amp;hellip; 그러다 발견한 기능이 바로 Jenkins Pipeline!&lt;/p>
&lt;p>　이번 포스팅에서는 Jenkins Job을 동시에 여러 번 사용해야 하는 경우를 Pipeline을 통해서 개선한 내용에 대하여 공유해보려 한다. Jenkins Pipeline에 대해 들어만 봤는데 이번에 실제로 사용해보니 생각보다 쉽게 개선할 수 있었고 옵션들을 상황에 맞게 조합을 잘 한다면 상당히 활용성이 높아 보이는 기능인 것 같다.&lt;/p>
&lt;h2 id="기존상황">기존상황&lt;/h2>
&lt;p>　테﻿스트를 위해 임의로 느린 응답을 생성하도록 URL을 구성하고 위에서 이야기했던 것처럼 Job 하나에 아주 심플하게 Python script를 작성하고 실행해보도록 하자. 임의로 느린 응답은 &lt;a href="http://slowwly.robertomurray.co.uk/" target="_blank" rel="noopener noreffer ">http://slowwly.robertomurray.co.uk/&lt;/a> 에서 제공하는 기능을 활용하였다.﻿&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">requests&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">urls&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="s2">&amp;#34;http://slowwly.robertomurray.co.uk/delay/0/url/https://www.naver.com/&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="s2">&amp;#34;http://slowwly.robertomurray.co.uk/delay/100/url/https://www.naver.com/&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;http://slowwly.robertomurray.co.uk/delay/200/url/https://www.naver.com/&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;http://slowwly.robertomurray.co.uk/delay/500/url/https://www.naver.com/&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;http://slowwly.robertomurray.co.uk/delay/1000/url/https://www.naver.com/&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;http://slowwly.robertomurray.co.uk/delay/2000/url/https://www.naver.com/&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;http://slowwly.robertomurray.co.uk/delay/5000/url/https://www.naver.com/&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;http://slowwly.robertomurray.co.uk/delay/10000/url/https://www.naver.com/&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;http://slowwly.robertomurray.co.uk/delay/20000/url/https://www.naver.com/&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="n">url&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">urls&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="n">status_code&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">requests&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">status_code&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="k">if&lt;/span> &lt;span class="n">status_code&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="mi">200&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s1">&amp;#39;응답 실패 :&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s1">, status : &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">status_code&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s1">&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="n">exit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s1">&amp;#39;응답성공 : &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s1">&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>그래서 실행해보면 50초가 소요되었다. 자, 이제 개선을 해보자!&lt;/p>
&lt;h2 id="개선을-해보자">개선을 해보자&lt;/h2>
&lt;p>　﻿전체적인 개선의 흐름은 하나의 Job에 모니터링하고자 하는 url을 파라미터로 받아서 처리할 수 있도록 설정하고, 이를 Jenkins Pipeline 을 통해 여러 URL을 동시에 모니터링하게 구성하는 것이다. 그러면 두 개의 Job(파라미터로 받아 모니터링하는 Job, Jenkins Pipeline Job) 만으로 보다 빠르고 효율적인 구성을 할 수 있을 것으로 상상을 하고.&lt;/p>
&lt;h3 id="job을-범용적으로-jenkins-paramters-활용">Job을 범용적으로 (Jenkins paramters 활용)&lt;/h3>
&lt;p>　﻿위에서 샘플로 작성하였던 Python script는 url 이 늘어날수록 Job 안에 script를 수정해야 한다. 그렇게 해도 무방하지만 이번 개선의 목표는 하나의 Job을 Pipeline 이 병렬로 컨트롤하도록 설정해야 했기 때문에 Jenkins Job에 파라미터를 받을 수 있도록 아래처럼 Jenkins Job 설정에 파라미터를 설정하고 Python script 또한 수정해 주자.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/jenkins-job-parallel-processing-by-pipeline/jenkins-parameter-option.jpg" title="/images/jenkins-job-parallel-processing-by-pipeline/jenkins-parameter-option.jpg" data-thumbnail="/images/jenkins-job-parallel-processing-by-pipeline/jenkins-parameter-option.jpg" data-sub-html="&lt;h2>﻿Job &amp;gt; 구성 &amp;gt; 이 빌드는 매개변수가 있습니다&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/jenkins-job-parallel-processing-by-pipeline/jenkins-parameter-option.jpg"
 data-srcset="https://taetaetae.github.io/images/jenkins-job-parallel-processing-by-pipeline/jenkins-parameter-option.jpg, https://taetaetae.github.io/images/jenkins-job-parallel-processing-by-pipeline/jenkins-parameter-option.jpg 1.5x, https://taetaetae.github.io/images/jenkins-job-parallel-processing-by-pipeline/jenkins-parameter-option.jpg 2x"
 data-sizes="auto"
 alt="/images/jenkins-job-parallel-processing-by-pipeline/jenkins-parameter-option.jpg" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">﻿Job &amp;gt; 구성 &amp;gt; &lt;code>이 빌드는 매개변수가 있습니다&lt;/code>&lt;/figcaption>
 &lt;/figure>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">requests&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="nn">os&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">environ&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;url&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">status_code&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">requests&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">status_code&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="n">status_code&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="mi">200&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s1">&amp;#39;응답 실패 :&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s1">, status : &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">status_code&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s1">&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">exit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s1">&amp;#39;응답성공 : &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s1">&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="병렬-실행을-위한-jenkins-설정">병렬 실행을 위한 Jenkins 설정&lt;/h3>
&lt;p>　﻿Jenkins Job 을 생성하면 기본적으로 Job마다의 대기열(Queue)이 있어 Job이 실행 중이라면 시작된 시간 순서대로 기다렸다가 앞선 Job이 종료가 되면 이어서 실행되는 구조이다. 하지만 우리는 Job을 병렬로 실행해야 했기에 Job 설정 중 &lt;code>필요한 경우 concurrent 빌드 실행&lt;/code> 옵션을 켜줘서 기다리지 않고 병렬로 실행될 수 있도록 해준다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/jenkins-job-parallel-processing-by-pipeline/jenkins-concurrent-build-option.jpg" title="/images/jenkins-job-parallel-processing-by-pipeline/jenkins-concurrent-build-option.jpg" data-thumbnail="/images/jenkins-job-parallel-processing-by-pipeline/jenkins-concurrent-build-option.jpg" data-sub-html="&lt;h2>﻿Job &amp;gt; 구성 &amp;gt; 필요한 경우 concurrent 빌드 실행&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/jenkins-job-parallel-processing-by-pipeline/jenkins-concurrent-build-option.jpg"
 data-srcset="https://taetaetae.github.io/images/jenkins-job-parallel-processing-by-pipeline/jenkins-concurrent-build-option.jpg, https://taetaetae.github.io/images/jenkins-job-parallel-processing-by-pipeline/jenkins-concurrent-build-option.jpg 1.5x, https://taetaetae.github.io/images/jenkins-job-parallel-processing-by-pipeline/jenkins-concurrent-build-option.jpg 2x"
 data-sizes="auto"
 alt="/images/jenkins-job-parallel-processing-by-pipeline/jenkins-concurrent-build-option.jpg" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">﻿Job &amp;gt; 구성 &amp;gt; &lt;code>필요한 경우 concurrent 빌드 실행&lt;/code>&lt;/figcaption>
 &lt;/figure>
&lt;p>　﻿또한 Jenkins Job 자체는 병렬로 실행되도록 설정되었다 해도 기본적으로 Jenkins 자체의 대기열은 한정되어 있기 때문에 적당히 늘려줘서 여러 개의 Job이 대기 열 없이 동시에 실행될 수 있도록 해준다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/jenkins-job-parallel-processing-by-pipeline/jenkins-executors.jpg" title="/images/jenkins-job-parallel-processing-by-pipeline/jenkins-executors.jpg" data-thumbnail="/images/jenkins-job-parallel-processing-by-pipeline/jenkins-executors.jpg" data-sub-html="&lt;h2>﻿Jenkins &amp;gt; Jenkins 관리 &amp;gt; 시스템 설정 &amp;gt; of executors&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/jenkins-job-parallel-processing-by-pipeline/jenkins-executors.jpg"
 data-srcset="https://taetaetae.github.io/images/jenkins-job-parallel-processing-by-pipeline/jenkins-executors.jpg, https://taetaetae.github.io/images/jenkins-job-parallel-processing-by-pipeline/jenkins-executors.jpg 1.5x, https://taetaetae.github.io/images/jenkins-job-parallel-processing-by-pipeline/jenkins-executors.jpg 2x"
 data-sizes="auto"
 alt="/images/jenkins-job-parallel-processing-by-pipeline/jenkins-executors.jpg" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">﻿Jenkins &amp;gt; Jenkins 관리 &amp;gt; 시스템 설정 &amp;gt; &lt;code>of executors&lt;/code>&lt;/figcaption>
 &lt;/figure>
&lt;h3 id="jenkins-pipeline">Jenkins Pipeline&lt;/h3>
&lt;p>　﻿Job 을 Pipeline으로 만들고 Pipeline scirpt를 작성하는데 눈여겨봐야 할 옵션들을 짚고 넘어가고자 한다.&lt;/p></description></item><item><title>빌드/테스트는 내가 해줄게. 너는 코딩에 집중해 (by GitHub Pull Request Builder)</title><link>https://taetaetae.github.io/2020/09/07/github-pullrequest-build/</link><pubDate>Mon, 07 Sep 2020 10:09:56 +0000</pubDate><guid>https://taetaetae.github.io/2020/09/07/github-pullrequest-build/</guid><description>&lt;p>　git 은 분산 버전 관리 시스템 중 가장 잘 알려져 있다고 해도 과언이 아닐 정도로 대부분의 시스템에서 사용되고 있는 것 같다. 이를 웹서비스에서 보다 편하게 사용할 수 있도록 한 시스템이 Github. &lt;!--more -->Github 을 사용하는 이유 중에 가장 큰 이유를 하나만 이야기해보자면 바로 온라인상에서 코드 리뷰를 할 수 있는 pullRequest라는 기능 때문이 아닐까 조심스럽게 생각을 해본다.&lt;/p>
&lt;p>　pullRequest는 work branch에서 작업한 내용을 base branch로 merge 전 꼭 코드 리뷰가 아니더라도 작업한 내용에 대해서 다양한 검사를 자동화할 수 있는 강력한 기능들이 많다. 이러한 자동화는 CI(지속적 통합) 관점에서 매우 중요한데 코드에 대해 체크해야 할 부분들(빌드, 테스트, 정적 분석 등)을 &amp;ldquo;알아서&amp;rdquo; 해준다면 작업자는 오롯이 비즈니스 로직 개발에 대해서만 신경 쓸 수 있으니 생산성 절약 측면에서 엄청난 효과를 볼 수 있다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/github-pullrequest-build/car.gif" title="/images/github-pullrequest-build/car.gif" data-thumbnail="/images/github-pullrequest-build/car.gif" data-sub-html="&lt;h2>내가 하는일에만 집중할 수 있게! 출처 : https://www.clien.net/service/board/park/10453442&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/github-pullrequest-build/car.gif"
 data-srcset="https://taetaetae.github.io/images/github-pullrequest-build/car.gif, https://taetaetae.github.io/images/github-pullrequest-build/car.gif 1.5x, https://taetaetae.github.io/images/github-pullrequest-build/car.gif 2x"
 data-sizes="auto"
 alt="/images/github-pullrequest-build/car.gif" width="50%" />
 &lt;/a>&lt;figcaption class="image-caption">내가 하는일에만 집중할 수 있게! &lt;br> 출처 : &lt;a href="https://www.clien.net/service/board/park/10453442" target="_blank" rel="noopener noreffer ">https://www.clien.net/service/board/park/10453442&lt;/a>&lt;/figcaption>
 &lt;/figure>
&lt;p>이번 포스팅에서는 그중에서도 아주 간단한 설정만으로 work branch의 빌드 상태를 검사해 볼 수 있는 Jenkins의 Github Pull Request Builder를 설치 및 활용해 보고자 한다.&lt;/p>
&lt;blockquote>
&lt;p>사실 최근 팀에서 CI 서버를 이전해야 했었다. 머릿속에서는 어떻게 하면 되겠지 싶었지만 막상 해보려니 Jenkins 버전업도 되었고 뭐부터 해야 할지 허둥대는 필자가 부끄러웠다. 이참에 정리를 해보며 다시 한번 리마인드 하는 시간을 가져보고자 한다. (이래서 기억보다 기록이 중요하다.)&lt;/p>&lt;/blockquote>
&lt;h2 id="준비물">준비물&lt;/h2>
&lt;p>　전체적인 흐름은 아래 그림처럼 흘러가기 때문에 당연히 서버에 Jenkins 가 설치되어 있어야 한다. Jenkins 설치는 필자의 포스팅(&lt;a href="https://taetaetae.github.io/2018/12/02/jenkins-install/" target="_blank" rel="noopener noreffer ">Jenkins 설치 치트키&lt;/a>)를 참고해 보는 것도 좋을 것 같다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/github-pullrequest-build/programmer-github-jenkins.jpg" title="/images/github-pullrequest-build/programmer-github-jenkins.jpg" data-thumbnail="/images/github-pullrequest-build/programmer-github-jenkins.jpg" data-sub-html="&lt;h2>전체적인 흐름&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/github-pullrequest-build/programmer-github-jenkins.jpg"
 data-srcset="https://taetaetae.github.io/images/github-pullrequest-build/programmer-github-jenkins.jpg, https://taetaetae.github.io/images/github-pullrequest-build/programmer-github-jenkins.jpg 1.5x, https://taetaetae.github.io/images/github-pullrequest-build/programmer-github-jenkins.jpg 2x"
 data-sizes="auto"
 alt="/images/github-pullrequest-build/programmer-github-jenkins.jpg" width="50%" />
 &lt;/a>&lt;figcaption class="image-caption">전체적인 흐름&lt;/figcaption>
 &lt;/figure>
&lt;p>　참고로 필자는 GitHub Enterprise 버전에서 사용했는데 일반 Github에서도 동일한 방법으로 사용 가능하다.&lt;/p>
&lt;h3 id="github과-jenkins의-연동을-위한-2가지-설정">Github과 Jenkins의 연동을 위한 2가지 설정&lt;/h3>
&lt;p>　Github 과 Jenkins 가 통신이 되도록 설정해 줘야 한다. 그래야 Github의 코드를 받아서 Jenkins 가 빌드를 하고 그 빌드 결과를 다시 Github에 리포트가 가능해지기 때문이다. 먼저 첫 번째로 ssh 설정으로 Github의 코드를 가져오도록 ssh 설정을 해두자. ssh 설정하는 방법은 필자의 포스팅(&lt;a href="https://taetaetae.github.io/2018/02/08/github-with-jenkins/" target="_blank" rel="noopener noreffer ">Github과 Jenkins 연동하기&lt;/a>)편을 확인해보면 될 것 같다.&lt;/p>
&lt;p>　그다음으로 아래에서 이야기할 &lt;code>GitHub Pull Request Builder&lt;/code>라는 Jenkins plugin 이 빌드가 끝난 뒤에 결과를 리포팅 해줄 수 있는 인증 토큰을 발급받아두자. Github &amp;gt; Settings &amp;gt; Developer settings &amp;gt; Personal access tokens 화면에서 키를 생성하고 만들어진 키를 저장해 둔다. (이 키는 보안에 유의해야 하고, 화면 경고(?)에서도 볼 수 있듯이 키는 생성 시 한 번밖에 볼 수 없기 때문에 미리 저장해 둬야 한다.)&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/github-pullrequest-build/github-access-token.jpg" title="/images/github-pullrequest-build/github-access-token.jpg" data-thumbnail="/images/github-pullrequest-build/github-access-token.jpg" data-sub-html="&lt;h2>인증토큰을 미리 받아두자.&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/github-pullrequest-build/github-access-token.jpg"
 data-srcset="https://taetaetae.github.io/images/github-pullrequest-build/github-access-token.jpg, https://taetaetae.github.io/images/github-pullrequest-build/github-access-token.jpg 1.5x, https://taetaetae.github.io/images/github-pullrequest-build/github-access-token.jpg 2x"
 data-sizes="auto"
 alt="/images/github-pullrequest-build/github-access-token.jpg" width="50%" />
 &lt;/a>&lt;figcaption class="image-caption">인증토큰을 미리 받아두자.&lt;/figcaption>
 &lt;/figure>
&lt;h2 id="jenkins-설정">Jenkins 설정&lt;/h2>
&lt;p>　Jenkins &amp;gt; 관리 &amp;gt; pluginManager에 들어가 &lt;code>GitHub Pull Request Builder&lt;/code>를 검색 후 설치해 준다. 그러고 나서 Jenkins &amp;gt; 관리 &amp;gt; 환경설정에 들어가 보면 아래와 같이 &lt;code>GitHub Pull Request Builder&lt;/code> 항목이 생긴 것을 확인할 수 있고 위에서 설정한 인증토큰을 아래처럼 등록 후 저장을 한다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/github-pullrequest-build/add-github-access-token.jpg" title="/images/github-pullrequest-build/add-github-access-token.jpg" data-thumbnail="/images/github-pullrequest-build/add-github-access-token.jpg" data-sub-html="&lt;h2>credentials 을 위에서 발급받은 인증토큰으로 등록해준다.&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/github-pullrequest-build/add-github-access-token.jpg"
 data-srcset="https://taetaetae.github.io/images/github-pullrequest-build/add-github-access-token.jpg, https://taetaetae.github.io/images/github-pullrequest-build/add-github-access-token.jpg 1.5x, https://taetaetae.github.io/images/github-pullrequest-build/add-github-access-token.jpg 2x"
 data-sizes="auto"
 alt="/images/github-pullrequest-build/add-github-access-token.jpg" width="50%" />
 &lt;/a>&lt;figcaption class="image-caption">credentials 을 위에서 발급받은 인증토큰으로 등록해준다.&lt;/figcaption>
 &lt;/figure>
&lt;p>　Jenkins job을 하나 만들고 pullRequest 가 발생했을 때 자동으로 실행될 수 있도록 설정을 해준다. 먼저 General 탭에 &lt;code>Github project&lt;/code>에 Github url 을 적어주고&lt;/p>
&lt;a class="lightgallery" href="https://taetaetae.github.io/images/github-pullrequest-build/jenkins-general.jpg" title="/images/github-pullrequest-build/jenkins-general.jpg" data-thumbnail="/images/github-pullrequest-build/jenkins-general.jpg">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/github-pullrequest-build/jenkins-general.jpg"
 data-srcset="https://taetaetae.github.io/images/github-pullrequest-build/jenkins-general.jpg, https://taetaetae.github.io/images/github-pullrequest-build/jenkins-general.jpg 1.5x, https://taetaetae.github.io/images/github-pullrequest-build/jenkins-general.jpg 2x"
 data-sizes="auto"
 alt="/images/github-pullrequest-build/jenkins-general.jpg" width="50%" />
 &lt;/a>
&lt;p>　소스 코드 관리 탭에서 ssh 주소를 적고 위에서 미리 설정한 ssh 키로 credentials 값을 넣어준다. 전에도 이야기했지만 이 부분에서 오류가 발생하면 빨간색 글씨로 오류 내용이 나오고 아래 화면처럼 오류가 없다면 아무것도 안 나온다. Refspec 에 &lt;code>+refs/pull/*:refs/remotes/origin/pr/*&lt;/code> 라고 적어주고 브랜치 설정은 파라미터로 받아와서 pullRequest를 발생시킨 브랜치를 빌드 할 수 있도록 &lt;code>${sha1}&lt;/code> 라고 적어주자.&lt;/p></description></item><item><title>더이상 기다리지 않아도 되는 배치 무중단 배포</title><link>https://taetaetae.github.io/2019/10/13/batch-nondisruptive-deploy/</link><pubDate>Sun, 13 Oct 2019 15:46:12 +0000</pubDate><guid>https://taetaetae.github.io/2019/10/13/batch-nondisruptive-deploy/</guid><description>&lt;p>&lt;a href="https://taetaetae.github.io/2019/09/29/woowabros-spring-batch/" target="_blank" rel="noopener noreffer ">지난 포스팅&lt;/a>, 그러니까 우아한 형제들에서 초대를 받아 Spring batch 에 대한 테크세미나에 다녀 왔다. 그 중 가장 인상깊었던 부분이 바로 &lt;code>무중단 배포&lt;/code>. 차일피일 미루다 필자가 속한 팀에서도 배포때마다 가장 불편을 느끼고 있었던 부분이었기도 했고&lt;!--more -->, &lt;code>그런가보다&lt;/code> 하며 개념만 알고 넘어가기엔 무언가 양심에 찔려 직접 무중단 배포를 할 수 있도록 구성을 해보고 테스트까지 해보고자 한다.&lt;/p>
&lt;h2 id="상황-및-문제점">상황 및 문제점&lt;/h2>
&lt;p>리눅스 서버에 Jenkins가 설치되어 있고, Spring batch 모듈을 실행시키고 있다. 수동으로 실행을 하거나, Jenkins RestApi를 이용해서 실행을 할 수 있지만 주로 정해진 시간 즉, 스케쥴링에 의해 실행되곤 한다. 스케쥴링의 가장 작은 단위는 1분단위 배치도 있기 때문에 24시간 멈추지 않고 실행되고 있다고 무방하다. 하지만 배치 모듈이 수정되고, 배포를 하기 위해서는 다음과 같은 시나리오로 진행이 된다.&lt;/p>
&lt;ol>
&lt;li>Jenkins 설정의 &lt;code>끄기전 준비&lt;/code> 를 실행하여 더이상 Jenkins에 의해 Spring batch 모듈(이하 Job)이 실행되지 않도록 한다.&lt;/li>
&lt;li>새로운 Job은 더이상 실행되지 않지만 이미 실행중이였던 Job 은 강제로 중단을 하거나 Job 이 끝날때까지 기다린다.&lt;/li>
&lt;li>실행중인 Job이 없을 경우 이제 배포를 진행한다.&lt;/li>
&lt;li>배포가 완료되면 Jenkins 설정의 &lt;code>끄기전 준비&lt;/code>를 해제한다.&lt;/li>
&lt;/ol>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/batch-nondisruptive-deploy/wait.jpg" title="/images/batch-nondisruptive-deploy/wait.jpg" data-thumbnail="/images/batch-nondisruptive-deploy/wait.jpg" data-sub-html="&lt;h2>실행중인 Job이 안끝나면 마냥 기다릴텐가? 출처 : https://m.post.naver.com/viewer/postView.nhn?volumeNo=14100660&amp;memberNo=2032633&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/batch-nondisruptive-deploy/wait.jpg"
 data-srcset="https://taetaetae.github.io/images/batch-nondisruptive-deploy/wait.jpg, https://taetaetae.github.io/images/batch-nondisruptive-deploy/wait.jpg 1.5x, https://taetaetae.github.io/images/batch-nondisruptive-deploy/wait.jpg 2x"
 data-sizes="auto"
 alt="/images/batch-nondisruptive-deploy/wait.jpg" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">실행중인 Job이 안끝나면 마냥 기다릴텐가? &lt;br>출처 : &lt;a href="https://m.post.naver.com/viewer/postView.nhn?volumeNo=14100660&amp;amp;memberNo=2032633" target="_blank" rel="noopener noreffer ">https://m.post.naver.com/viewer/postView.nhn?volumeNo=14100660&amp;memberNo=2032633&lt;/a>&lt;/figcaption>
 &lt;/figure>
&lt;p>실행되는 Job을 중단하지 못하는 상황 즉, 실행중에 중단하면 트랜잭션이 깨져 무조건 기다려야만 하는 상황이라면 배포 또한 계속 지연될 수 밖에 없는 상황인 것이다. Spring boot에 java config 를 활용하고 딱 &lt;code>jar&lt;/code> 파일 하나를 실행하는 방식이라면 &lt;code>jar&lt;/code>파일을 바꿔치기 하는 식으로 고민을 해볼수도 있을것 같다. 하지만 Legacy 코드가 아직 존재하여 일반 Spring 에 xml 로 config 하는 방식으로 운영중이라 &lt;code>jar&lt;/code>파일 하나만 바꿔치기 하기엔 무리가 있는 상황.&lt;/p>
&lt;p>은총알처럼 어디에서나 사용이 가능한 만병통치약 같은 방법은 없다. 언제나 그랬듯 현재 시스템(xml config 방식)에 가장 최적화된 방법, 그리고 java config 방식에서도 사용이 가능할것 같은 방법을 생각해 보았다.&lt;/p>
&lt;h2 id="무중단-배포를-가능케-하는-3가지-핵심">무중단 배포를 가능케 하는 3가지 핵심&lt;/h2>
&lt;p>&lt;strong>1. 배포를 매번 새로운 경로에 배포한다.&lt;/strong>
각 회사마다, 그리고 서비스마다 정말 다양한 배포 시스템이 있다. 그들의 공통점은 원격서버의 &lt;code>특정 경로&lt;/code>에 빌드된 파일들을 밀어 넣어준다는 것. 시나리오는 다음과 같다.&lt;/p>
&lt;ol>
&lt;li>배포할때마다 별도의 디렉토리를 생성한뒤 심볼릭 링크를 연결해준다.&lt;/li>
&lt;li>배포는 &lt;code>1&lt;/code>에서 연결한 심볼릭 링크에 배포되도록 설정, 결국 매번 만들어지는 디렉토리에 배포가 되게 된다.&lt;/li>
&lt;/ol>
&lt;p>여기서 중요한점은 &amp;ldquo;배포할 때마다 새로운 디렉토리에 배포가 된다&amp;rdquo; 와 배포시에는 항상 심볼릭 링크에만 배포를 하면 되기 때문에 &amp;ldquo;배포시스템이 새로 만들어지는 디렉토리의 경로를 몰라도 무방하다&amp;quot;는 점이다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#!/bin/sh
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>&lt;span class="nb">cd&lt;/span> /~~~/deploy/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 임시 디렉토리&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">DIRECTORY_NAME&lt;/span>&lt;span class="o">=&lt;/span>batch_&lt;span class="k">$(&lt;/span>/bin/date +%Y%m%d%H%M%S&lt;span class="k">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mkdir &lt;span class="nv">$DIRECTORY_NAME&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>위 쉘 스크립트를 실행하면 batch_20191012205218 와 같은 디렉토리가 생성이 된다. 심볼릭 링크 관련해서는 바로 아래 이어서 설명하겠다.&lt;/p>
&lt;p>&lt;strong>2. 심볼릭 링크의 원래 링크를 즉시 변경&lt;/strong>
보통 심볼릭 링크 (즉, 바로가기) 의 경로를 변경하기 위해서는 아래처럼 지웠다가 삭제하는 식으로 했었는데&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ mkdir directory_a
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ mkdir directory_b
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ ln -s directory_a asdf
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ ll
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">asdf -&amp;gt; directory_a
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">directory_a
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">directory_b
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># directory_a 에서 directory_b 로 바꾸는 경우 (심볼릭 링크 자체를 삭제하고 다시 심볼릭 링크 생성)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ rm asdf
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ ln -s directory_b asdf
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ ll
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">asdf -&amp;gt; directory_b
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">directory_a
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">directory_b
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>이렇게 되면 삭제하고 ~ 다시 만들어지는 타이밍에 배포가 되거나 실행이 되는 즉, 해당 경로에 엑세스 하는 경우 이전의 경로를 바라본다거나 의도했던 방식으로 실행이 되지 않는 상황이 발생한다. (찰나의 타이밍 이지만 필자는 이러한 문제로 이전의 경로를 바라보는 문제가 발생했었다.) 그래서 ln 의 옵션중인 &lt;code>-Tfs&lt;/code>옵션으로 즉시 변경을 해주도록 하자. (&lt;a href="https://linux.die.net/man/1/ln" target="_blank" rel="noopener noreffer ">ln man 참고&lt;/a>)&lt;/p></description></item><item><title>우아한 스프링 배치 테크세미나 정리 및 후기 (by 우아한 형제들)</title><link>https://taetaetae.github.io/2019/09/29/woowabros-spring-batch/</link><pubDate>Sun, 29 Sep 2019 17:55:50 +0000</pubDate><guid>https://taetaetae.github.io/2019/09/29/woowabros-spring-batch/</guid><description>&lt;p>지난주 우아한 형제들에서 진행하였던 &amp;ldquo;9월 우아한 테크 세미나 - 우아한 스프링 배치&amp;rdquo; 에 다녀왔다. 필자에게 이번 9월은 정신이 어디에 있는지 모를만큼 바쁘고 힘들었지만 예전부터 궁금하기도 했고 &lt;!--more -->요즘들어 관심을 갖던 &amp;ldquo;배치 어플리케이션&amp;quot;을 어떻게 하면 &amp;ldquo;우아한 방법&amp;quot;으로 사용할 수 있을지에 대해 여러 생각들이 있었기에 큰 기대를 가지고 지옥철을 견디며 잠실 근처에 있는 우아한 형제들 작은집으로 가게 되었다.
어떤 내용을 발표하였는지에 대해 &lt;code>기억잘하는 똑똑한 앵무새&lt;/code>가 되어 정리하기 보다 주요 포인트에 대한 생각과 함께 참여를 못한 분들 위해서라기 보다 내 스스로 정리를 하기 위해 포스팅을 작성해 보고자 한다.
(이번에도 불러주셔서 감사합니다 ^=^)&lt;/p>
&lt;h2 id="인트로">인트로&lt;/h2>
&lt;p>연사자 분은 워낙에 유명하신 분이라 별도의 설명이 필요 없이 운영하시는 &lt;a href="https://jojoldu.tistory.com" target="_blank" rel="noopener noreffer ">블로그 주소&lt;/a>로 대체를 해본다. 이번 행사에 초대되신 분들은 한번이라도 스프링 배치를 써분 분들을 대상으로 진행하게 되었다고 했는데 마침 필자도 팀 내에서 운영하고 있는 배치 어플리케이션을 보다 효율적이고 우아하게 바꿔보고자 하는 니즈가 있었기에 아마 초대된게 아닐까 싶다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/woowabros-spring-batch/small_house.jpg" title="/images/woowabros-spring-batch/small_house.jpg" data-thumbnail="/images/woowabros-spring-batch/small_house.jpg" data-sub-html="&lt;h2>아기자기한 우아한 형제들 건물 내부&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/woowabros-spring-batch/small_house.jpg"
 data-srcset="https://taetaetae.github.io/images/woowabros-spring-batch/small_house.jpg, https://taetaetae.github.io/images/woowabros-spring-batch/small_house.jpg 1.5x, https://taetaetae.github.io/images/woowabros-spring-batch/small_house.jpg 2x"
 data-sizes="auto"
 alt="/images/woowabros-spring-batch/small_house.jpg" width="50%" />
 &lt;/a>&lt;figcaption class="image-caption">아기자기한 우아한 형제들 건물 내부&lt;/figcaption>
 &lt;/figure>
&lt;p>더불어 발표전에 간략히 회사가 원하는 인재에 대하여 언급해주셨는데 그게 어찌나 공감이 가던지. 역시 생각이 남다른 회사구나 하고 다시한번 생각을.&lt;/p>
&lt;blockquote>
&lt;p>자기보다 경험이 &amp;ldquo;적은&amp;rdquo; 사람에게 &amp;ldquo;설득을 당할 수&amp;rdquo; 있어야 하고, 자기보다 경험이 &amp;ldquo;많은 사람을 설득&amp;rdquo; 시킬 수 있어야 한다.&lt;/p>&lt;/blockquote>
&lt;h2 id="기본편">기본편&lt;/h2>
&lt;p>배치 어플리케이션이란 컴퓨터에서 사람와 상호작용없이 이어지는 프로그램(작업)들의 실행이라고 &lt;a href="https://ko.wikipedia.org/wiki/%EC%9D%BC%EA%B4%84_%EC%B2%98%EB%A6%AC" target="_blank" rel="noopener noreffer ">위키피디아&lt;/a>에 간결&amp;amp;명료하게 정리되어 있다. 그만큼 일반적인 웹 어플리케이션과의 차이가 있는데 웹 어플리케이션은 실시간 처리가 기본이고 요청에 대한 응답을 제공해야 하니 아무래도 속도가 상대적이며 QA시 편한 부분이 있다. 그에 반해 배치 어플리케이션은 웹 어플리케이션에서 말하는 요청이라는 개념보다 후속처리에 가깝고, 속도 또한 절대적이며 QA가 복잡하다는게 특징이다. 따라서 테스트코드는 웹 어플리케이션 보다 배치 어플리케이션이 더 필요하다고 볼 수 있다.
배치 어플리케이션이 필요한 상황은 크게 두가지로 나눠 볼 수가 있다고 한다.&lt;/p>
&lt;ul>
&lt;li>일정 주기로 실행 되어야 할 때&lt;/li>
&lt;li>실시간 처리가 어려운 대량의 데이터를 처리 할때&lt;/li>
&lt;/ul>
&lt;p>평소 첫번째 상황만 생각하고 배치 어플리케이션을 작성하곤 했었는데 두번재 상황에 대해 생각에 생각을 더 해보니 스프링 배치를 간단하게만 (Tasklet) 사용하고 있는건 아닌가 하는 반성을 해보곤 했다. (Reader, Processor, Writer 등 다양한 레이어가 있는데도&amp;hellip;)&lt;/p>
&lt;p>특히 스프링 배치에서는 기본적으로 모든 데이터를 메모리에 쌓지 않는 조회방식라고 한다. (DB기준) Paging 혹은 Cursor로 pageSize만큼만 읽어오고 chunkSize만큼만 commit 하는 형태. 이러한 각 레이어별 size를 잘 조정하기만 해도 적은 노력으로 큰 성능을 얻을 수 있는 부분이 프레임워크를 사용하는 이유 아닐까 라고 생각해본다.&lt;/p>
&lt;p>또한 &lt;code>@JobScope&lt;/code> 나 &lt;code>@StepScope&lt;/code>는 Late Binding 즉 배치 어플리케이션이 실행되는 시점이 아니라 Job 이 실행될때 생성이 되기 때문에 이를 활용하여 동적으로 reader / processor / wirter 레이어를 만들 수 있다고 한다.&lt;/p>
&lt;h2 id="활용편">활용편&lt;/h2>
&lt;p>스프링 배치를 이용한 배치 어플리케이션이 있고 이를 스케쥴링 등 관리를 해주는 도구들에 이야기를 해주셨다.&lt;/p>
&lt;ul>
&lt;li>Cron
&lt;ul>
&lt;li>리눅스를 어느정도 사용해봤다면 알만한 리눅스 기본 스케쥴링 프로그램인 Cron.&lt;/li>
&lt;li>필자도 Cron 으로 주기적으로 실행하도록 설정해보기도 하였지만 배치 어플리케이션의 특성상 로그 및 실행/종료 등 제한사항이 많은 건 사실인것 같다.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Spring MVC + API Call
&lt;ul>
&lt;li>주변에서 사용하고 있다고 하던 방식. 이 방식의 장점은 항상 떠있기 때문에 어플리케이션 구동시간이 별도로 필요 없다는 장점이 있지만 전반적인 관리가 어려운 단점이 있는것 같다.&lt;/li>
&lt;li>물론 울며 겨자먹기 식으로 단점을 극복할 방법은 여러가지가 있겠지만 모든건 항상 Trade off&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Spring Batch Admin (Deprecated)
&lt;ul>
&lt;li>예전 팀분이 알려주셔서 잠깐 봤던 부분이긴 한데 어느사이에 Deprecated 되었다고 한다.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Quertz + Admin
&lt;ul>
&lt;li>&lt;a href="http://www.quartz-scheduler.org/" target="_blank" rel="noopener noreffer ">http://www.quartz-scheduler.org/&lt;/a>&lt;/li>
&lt;li>아주 오래전에 써본 기억이 있지만 배보다 배꼽이 더 큰 상황같았던 힘들었던 기억들만 남아있는 구현방법인것 같다. 여러 레이어를 혼용해서 쓰다보면 각 레이어간의 상호 연결성의 위배되는 경우가 많기에&amp;hellip;&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>CI Tools (Jenkins / Teamcity 등)
&lt;ul>
&lt;li>아무래도 가장 추천할만한게 CI Tool 인것 같다. 그중에 필자도 Jenkins라는 툴을 너무 좋아하고.&lt;/li>
&lt;li>유료 툴 중에 Teamcity 를 잠깐 언급해주셨는데 찾아보니 한번즈음 써보고 싶을만한 기능들이 있어보였다.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>Jenkins 의 장점은 &lt;del>말해뭐해&lt;/del> 정도로 배치 어플리케이션과 궁합이 너무 잘 맞는 툴인것 같다. (물론 다른 툴들도 있겠지만 필자&lt;code>개취&lt;/code>라 넘어가도록 하자.) 특히 실행시 필요한 플러그인들이 다양하게 많이 있고, 실행방법 또한 수동/스케쥴링 으로 다양하게 할 수가 있으며 RestAPI 지원과 보안, 실행이력관리, 로그 등 최적화 되어있다고 해도 과언이 아닐정도로 다양한 장점들이 있는것 같다.&lt;/p></description></item><item><title>Jenkins 업그레이드 및 Master-Slave 구성</title><link>https://taetaetae.github.io/2019/03/17/jenkins-upgrade-master-slave/</link><pubDate>Sun, 17 Mar 2019 18:23:03 +0000</pubDate><guid>https://taetaetae.github.io/2019/03/17/jenkins-upgrade-master-slave/</guid><description>&lt;p>어떠한 작업(Job)이 있다고 가정해보자. 이를 &amp;ldquo;정해진 시간에 주기적&amp;rdquo; 이나 &amp;ldquo;필요할때&amp;rdquo; 작업을 수행하고 싶다면 어떤 툴(Tool)이 떠오르는가? &lt;!-- more -->그리고 이 작업(Job)들의 실행이력 등 전체적으로 관리하고 필요에 따라 다양한 플러그인을 활용하여 입맛에 맞는 작업(Job)으로 구성하고 싶을때 가장 첫번째로 떠오르는 툴은 바로 &amp;ldquo;Jenkins&amp;rdquo; 다. (극히 필자 개인적인 생각일수도 있지만&amp;hellip; ) 물론 리눅스 기반의 crontab 이나 다른 스케쥴러를 활용할수도 있다. 다만 필자 개인적인 느낌으로 나만의 Jarvis(?)처럼 내가 원하는데로 설정만 해두면 정해진 시간에 수행하고 그 결과를 로그로 남겨놓고 문제가 발생했을때 알림도 받을수 있으니 너무 좋은 툴이라 생각이 든다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/jenkins-upgrade-master-slave/ColossalSociableBuffalo-size_restricted.gif" title="/images/jenkins-upgrade-master-slave/ColossalSociableBuffalo-size_restricted.gif" data-thumbnail="/images/jenkins-upgrade-master-slave/ColossalSociableBuffalo-size_restricted.gif" data-sub-html="&lt;h2>실제로 Jarvis가 있다면 얼마나 편할까출처 : https://gfycat.com/ko/colossalsociablebuffalo&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/jenkins-upgrade-master-slave/ColossalSociableBuffalo-size_restricted.gif"
 data-srcset="https://taetaetae.github.io/images/jenkins-upgrade-master-slave/ColossalSociableBuffalo-size_restricted.gif, https://taetaetae.github.io/images/jenkins-upgrade-master-slave/ColossalSociableBuffalo-size_restricted.gif 1.5x, https://taetaetae.github.io/images/jenkins-upgrade-master-slave/ColossalSociableBuffalo-size_restricted.gif 2x"
 data-sizes="auto"
 alt="/images/jenkins-upgrade-master-slave/ColossalSociableBuffalo-size_restricted.gif" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">실제로 Jarvis가 있다면 얼마나 편할까&lt;br>출처 : &lt;a href="https://gfycat.com/ko/colossalsociablebuffalo" target="_blank" rel="noopener noreffer ">https://gfycat.com/ko/colossalsociablebuffalo&lt;/a>&lt;/figcaption>
 &lt;/figure>
&lt;p>지난 &lt;a href="https://taetaetae.github.io/2018/12/02/jenkins-install/" target="_blank" rel="noopener noreffer ">포스팅&lt;/a>에서는 Jenkins 를 설치하는 방법에 대해 알아보았다. (정확히 말하면 치트키 수준의&amp;hellip; ) 이번 포스팅에서는 Jenkins에 노드를 추가하여 master-slave 분산환경으로 구성하는 방법과 Jenkins 버전을 업그레이드 하는 방법에 대해 정리해보고자 한다.&lt;/p>
&lt;blockquote>
&lt;p>마침 필자의 팀에서 젠킨스를 분산환경으로 운영하고 있었는데 버전은 1.x &amp;hellip; 간헐적으로 Jenkins 버전 이슈로 에러가 발생해서 업그레이드를 해야하는 상황이 생긴것이다. 시키지도 않은 일을 하면서 팀에 도움도 될겸, 포스팅도 할겸, 1석 2조 효과. 서버 환경은 CentOS 7.4 64Bit 에서 테스트 하였다.&lt;/p>&lt;/blockquote>
&lt;h2 id="jenkins-버전-업그레이드-하기">Jenkins 버전 업그레이드 하기&lt;/h2>
&lt;p>Jenkins를 업그레이드 하게되면 기존에 있었던 Jenkins의 환경설정은 어떻게 마이그레이션 할까? Job 실행기록들은 그냥 날려버려야 하나? 걱정을 하며 구글링을 해본다. 그러면 &amp;ldquo;안해본것에 대한 두려움&amp;rdquo; 을 갖는 필자의 마음이 무색할 정도로 너무 간단하게도 그냥 기존에 있던 war 파일을 최신버전으로 교체하고 재시작 하라고 나온다. 읭? 뭐이리 간단해? 대부분의 문제들은 지레 겁부터 먹고 실행에 옮기지 &lt;del>못해서&lt;/del> 않아서 해결을 하지 못하는게 절반 이상같다. 자, 바로 실행에 옮겨보자.
우선 버전 업그레이드를 테스트 하기 위해 일부러 &lt;a href="http://mirrors.jenkins.io/war-stable/" target="_blank" rel="noopener noreffer ">낮은버전&lt;/a>으로 설치를 해둔다. (필자는 1.609.1로 설치해봤다.) 그리고 버전 업그레이드 후 설정이 그대로 옮겨지는지를 확인하기위해 Security 설정을 해서 Jenkins 접근시 로그인 여부를 물어보록 설정해둔다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/jenkins-upgrade-master-slave/old_jenkins.jpg" title="/images/jenkins-upgrade-master-slave/old_jenkins.jpg" data-thumbnail="/images/jenkins-upgrade-master-slave/old_jenkins.jpg" data-sub-html="&lt;h2>우측 하단에 빨간영역으로 낮은버전이 설치된것을 확인할수 있다.&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/jenkins-upgrade-master-slave/old_jenkins.jpg"
 data-srcset="https://taetaetae.github.io/images/jenkins-upgrade-master-slave/old_jenkins.jpg, https://taetaetae.github.io/images/jenkins-upgrade-master-slave/old_jenkins.jpg 1.5x, https://taetaetae.github.io/images/jenkins-upgrade-master-slave/old_jenkins.jpg 2x"
 data-sizes="auto"
 alt="/images/jenkins-upgrade-master-slave/old_jenkins.jpg" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">우측 하단에 빨간영역으로 낮은버전이 설치된것을 확인할수 있다.&lt;/figcaption>
 &lt;/figure>
&lt;p>설정이 완료되었으면 최신버전의 war를 다운받아 교체하고 재시작을 해준다. 그러면 너무나도 간단하게 버전이 업그레이드가 된것을 확인할수 있다. 그리고 처음에 설정한 Security 설정까지 그대로 유지되는것 또한 확인이 가능하다. 물론 구 버전에서 설치되었던 플러그인들이 버전업이 되며 그에 따라 지원하지 않는 문제들이 생길 수 있는데 이 부분은 플러그인을 업그레이드를 해준다거나 각 상황에 맞는 대응을 해줘야 한다. 이렇게 해서 생각보다(?) 너무 간단하게 버전업이 완료되었다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/jenkins-upgrade-master-slave/upgrade_complete.jpg" title="/images/jenkins-upgrade-master-slave/upgrade_complete.jpg" data-thumbnail="/images/jenkins-upgrade-master-slave/upgrade_complete.jpg" data-sub-html="&lt;h2>업그레이드 후 플러그인 업그레이드도 동일하게 맞춰주는게 중요하다.&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/jenkins-upgrade-master-slave/upgrade_complete.jpg"
 data-srcset="https://taetaetae.github.io/images/jenkins-upgrade-master-slave/upgrade_complete.jpg, https://taetaetae.github.io/images/jenkins-upgrade-master-slave/upgrade_complete.jpg 1.5x, https://taetaetae.github.io/images/jenkins-upgrade-master-slave/upgrade_complete.jpg 2x"
 data-sizes="auto"
 alt="/images/jenkins-upgrade-master-slave/upgrade_complete.jpg" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">업그레이드 후 플러그인 업그레이드도 동일하게 맞춰주는게 중요하다.&lt;/figcaption>
 &lt;/figure>
&lt;h2 id="jenkins-분산환경-구성하기-노드-추가하기">Jenkins 분산환경 구성하기 (노드 추가하기)&lt;/h2>
&lt;p>이번엔 Jenkins를 분산환경으로 구성해보고자 한다. 이렇게 노드를 추가하며 분산환경을 구성하는 이유는 마스터-슬레이브(Master-Slave) 패턴의 장점을 얻고자 함이다. 마스터는 작업을 쪼개고 슬레이브로 구성된 노드에게 분배를 하게되면 슬레이브 서버는 마스터의 요청을 처리하고 리턴하게 된다. 마치 스타크래프트에서 일꾼을 늘려서 미네랄과 가스를 더 빨리 얻는것처럼 말이다.&lt;/p>
&lt;p>여기서 필자가 가장 많이 삽질한 부분. 슬레이브 서버를 추가하는데 슬레이브 서버가 되는 서버에 동일하게 젠킨스를 설치하고 그들을 모두 연결하려 했던것&amp;hellip; 마치 클러스터링 하는것처럼&amp;hellip; 당연히 Jenkins 들의 묶음형태(?) 가 되야 할것같은 생각으로 시도하였지만 엄청난 삽질의 연속이 되어버렸다. 알고보니 마스터 Jenkins에서 슬레이브 서버에 작업을 전달할수 있도록 연동만 시켜주면 자동으로 Agent를 마스터 Jenkins가 슬레이브 서버에 설치/실행을 하고 작업을 분할하는것을 확인할 수 있었다. 자, 그럼 시작해보자.&lt;/p>
&lt;ol>
&lt;li>마스터 서버에서 공개키와 개인키 생성
먼저 마스터 서버와 슬레이브 서버를 SSH로 통신할수 있도록 SSH 키 설정을 해준다. 통상 홈 디렉토리 하위 .ssh 폴더에서 생성한다.&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">ssh 키 생성
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ ssh-keygen -t rsa
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Generating public/private rsa key pair.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Enter file in which to save the key &lt;span class="o">(&lt;/span>/~/.ssh/id_rsa&lt;span class="o">)&lt;/span>:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Enter passphrase &lt;span class="o">(&lt;/span>empty &lt;span class="k">for&lt;/span> no passphrase&lt;span class="o">)&lt;/span>:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Enter same passphrase again:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Your identification has been saved in /~/.ssh/id_rsa.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Your public key has been saved in /~/.ssh/id_rsa.pub.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">The key fingerprint is:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">SHA256:~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ user@hostname
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">The key&lt;span class="err">&amp;#39;&lt;/span>s randomart image is:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+---&lt;span class="o">[&lt;/span>RSA 2048&lt;span class="o">]&lt;/span>----+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span>oo. . &lt;span class="p">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span>o... o + &lt;span class="p">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span>. .o o+.o &lt;span class="p">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span>.++++. +o+o.. &lt;span class="p">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span>o.+*&lt;span class="o">=&lt;/span>.o.SEoo&lt;span class="o">=&lt;/span> &lt;span class="p">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span> . o+.*...+ + &lt;span class="p">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span> .. + +. + &lt;span class="p">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span> + . . &lt;span class="p">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span> ... &lt;span class="p">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+----&lt;span class="o">[&lt;/span>SHA256&lt;span class="o">]&lt;/span>-----+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">공개키 확인
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ cat id_rsa.pub
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ssh-rsa AAAAB3Nza~~~~~~~~eQKcx8B6uAflRm1J8In1 user@hostname
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ol start="2">
&lt;li>슬레이브 서버에서 마스터 서버에서 만든 공개키를 등록
슬레이브 서버에서는 마스터 서버에서 SSH 접속을 허용해야 하기때문에 마스터 서버에서 생성한 공개키를 등록해준다. 슬레이브 서버의 홈 디렉토리 하위 .ssh 폴더아래 파일을 만들고 위 공개키를 넣어주자.&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">$ vi authorized_keys
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ssh-rsa AAAAB3Nza~~~~~~~~eQKcx8B6uAflRm1J8In1 user@hostname
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ol start="3">
&lt;li>Jenkins 에서 Credentials 을 만들때 Private Key 설정을 &amp;ldquo;From the Jenkins master ~/.ssh&amp;quot;으로 설정한다. 나중에 이 정보로 인증을 처리한다.&lt;/li>
&lt;/ol>
&lt;a class="lightgallery" href="https://taetaetae.github.io/images/jenkins-upgrade-master-slave/jenkins_upgrade_3.jpg" title="/images/jenkins-upgrade-master-slave/jenkins_upgrade_3.jpg" data-thumbnail="/images/jenkins-upgrade-master-slave/jenkins_upgrade_3.jpg">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/jenkins-upgrade-master-slave/jenkins_upgrade_3.jpg"
 data-srcset="https://taetaetae.github.io/images/jenkins-upgrade-master-slave/jenkins_upgrade_3.jpg, https://taetaetae.github.io/images/jenkins-upgrade-master-slave/jenkins_upgrade_3.jpg 1.5x, https://taetaetae.github.io/images/jenkins-upgrade-master-slave/jenkins_upgrade_3.jpg 2x"
 data-sizes="auto"
 alt="/images/jenkins-upgrade-master-slave/jenkins_upgrade_3.jpg" width="80%" />
 &lt;/a>
&lt;ol start="4">
&lt;li>노드를 추가하고 조금 있으면 마스터 노드가 슬레이브 서버에 에이전트를 설치/실행하고 연동이 된것을 확인할수 있다.&lt;/li>
&lt;/ol>
&lt;a class="lightgallery" href="https://taetaetae.github.io/images/jenkins-upgrade-master-slave/jenkins_upgrade_4.jpg" title="/images/jenkins-upgrade-master-slave/jenkins_upgrade_4.jpg" data-thumbnail="/images/jenkins-upgrade-master-slave/jenkins_upgrade_4.jpg">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/jenkins-upgrade-master-slave/jenkins_upgrade_4.jpg"
 data-srcset="https://taetaetae.github.io/images/jenkins-upgrade-master-slave/jenkins_upgrade_4.jpg, https://taetaetae.github.io/images/jenkins-upgrade-master-slave/jenkins_upgrade_4.jpg 1.5x, https://taetaetae.github.io/images/jenkins-upgrade-master-slave/jenkins_upgrade_4.jpg 2x"
 data-sizes="auto"
 alt="/images/jenkins-upgrade-master-slave/jenkins_upgrade_4.jpg" width="80%" />
 &lt;/a>
&lt;p>실제로 슬레이브 서버에서 프로세스를 확인하면 아래처럼 에이전트( slave.jar )가 설치/실행되고 있는것을 확인할수 있다.&lt;/p></description></item><item><title>Jenkins 설치 치트키</title><link>https://taetaetae.github.io/2018/12/02/jenkins-install/</link><pubDate>Sun, 02 Dec 2018 04:37:59 +0000</pubDate><guid>https://taetaetae.github.io/2018/12/02/jenkins-install/</guid><description>&lt;p>&amp;ldquo;show me the money&amp;rdquo;, &amp;ldquo;black sheep wall&amp;rdquo;.
어렸을적 스타크래프트라는 게임이 나오고서 입에 달고 살았던 &lt;code>치트키&lt;/code>. 게임이 시작되고 해당 치트키를 입력하면 돈이 들어오거나 맵이 훤하게 보여 컴퓨터를 이기는데 도움을 주곤 했었다. &lt;!-- more -->
개발을 하면서 Jenkins는 나 대신 어떤 업무를 수행하는데 강력한 툴 중에 하나이다. (물론 만능이라는 소리는 아니지만&amp;hellip;) 새로운 프로젝트가 시작되거나 개발도중 무언가 자동화를 하고 싶을 경우엔 Jenkins를 찾게 되는데 그럴때마다 설치를 하고 이런저런 설정이 필요하다.
눈치를 챘을수도 있지만 이 포스트는 오로지 &lt;code>젠킨스 설치하는 방법&lt;/code>을 아주 간단하고 핵심만 정리하고자 한다. 마치 &lt;code>치트키&lt;/code>처럼.
나중에 다시 보기위해 + 누군가 해당 포스트를 보고 도움이 되었으면 하는 바람으로.&lt;/p>
&lt;p>(물론 이 방법밖에 있는건 아니지만 필자는 아래와 방법을 사용하고 있다.)&lt;/p>
&lt;hr>
&lt;p>우선 CentOS 환경에 Java가 설치되어 있는 상황이라 가정한다.&lt;/p>
&lt;ul>
&lt;li>적당한 위치에 tomcat 다운 ( &lt;a href="https://tomcat.apache.org/download-80.cgi" target="_blank" rel="noopener noreffer ">https://tomcat.apache.org/download-80.cgi&lt;/a> )
&lt;pre tabindex="0">&lt;code>wget {압축파일 다운경로, 필자는 apache-tomcat-8.5.35 }
&lt;/code>&lt;/pre>&lt;/li>
&lt;li>압축 해제후 하위 폴더중 webapps로 이동
&lt;pre tabindex="0">&lt;code>tar -zxvf apache-tomcat-8.5.35.tar.gz
cd apache-tomcat-8.5.35/webapps
&lt;/code>&lt;/pre>&lt;/li>
&lt;li>Jenkins 다운 ( &lt;a href="https://jenkins.io/download/" target="_blank" rel="noopener noreffer ">https://jenkins.io/download/&lt;/a> )
&lt;pre tabindex="0">&lt;code>wget http://mirrors.jenkins.io/war-stable/latest/jenkins.war
&lt;/code>&lt;/pre>&lt;/li>
&lt;li>tomcat 하위폴더중 conf 폴더로 이동
&lt;pre tabindex="0">&lt;code>cd ../conf
&lt;/code>&lt;/pre>&lt;/li>
&lt;li>server.xml 수정 및 http port 확인
&lt;pre tabindex="0">&lt;code>vi server.xml

&amp;lt;Host&amp;gt; 하위에 추가
&amp;lt;Context path=&amp;#34;/jenkins&amp;#34; debug=&amp;#34;0&amp;#34; privileged=&amp;#34;true&amp;#34; docBase=&amp;#34;jenkins.war&amp;#34; /&amp;gt;

port 확인
&amp;lt;Connector port=&amp;#34;8080&amp;#34; protocol=&amp;#34;HTTP/1.1&amp;#34;/&amp;gt;
&lt;/code>&lt;/pre>&lt;/li>
&lt;li>해당 서버의 ip와 위 port에 맞춰 url 입력후 jenkins 설치
&lt;pre tabindex="0">&lt;code>http://ip:8080/jenkins
&lt;/code>&lt;/pre>&lt;/li>
&lt;/ul></description></item><item><title>Jenkins에서 파이썬 출력을 실시간으로 보고싶다면?</title><link>https://taetaetae.github.io/2018/12/02/python-buffer/</link><pubDate>Sun, 02 Dec 2018 01:40:11 +0000</pubDate><guid>https://taetaetae.github.io/2018/12/02/python-buffer/</guid><description>&lt;p>필자가 운영하고 있는 &lt;a href="http://daily-devblog.com" target="_blank" rel="noopener noreffer ">Daily Dev Blog&lt;/a> 라는 서비스는 매일 동일한 시간에 주기적으로 데이터를 크롤링 하고 사용자에게 메일을 발송하는 일련의 작업을 수행하고 있다. 헌데 예상하지 못한 부분에서 예외가 발생하게 되면 어떤경우는 메일 발송을 못한다거나 기존에 발송했던 데이터를 다시 보내는 등 정상적이지 못한 상황을 맞이하게 된다.&lt;!-- more -->&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/python-buffer/ddb_duplication.png" title="/images/python-buffer/ddb_duplication.png" data-thumbnail="/images/python-buffer/ddb_duplication.png" data-sub-html="&lt;h2>메일이 하루라도 잘못오면 여기저기서 연락이 온다. 감사한 분들&amp;hellip;&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/python-buffer/ddb_duplication.png"
 data-srcset="https://taetaetae.github.io/images/python-buffer/ddb_duplication.png, https://taetaetae.github.io/images/python-buffer/ddb_duplication.png 1.5x, https://taetaetae.github.io/images/python-buffer/ddb_duplication.png 2x"
 data-sizes="auto"
 alt="/images/python-buffer/ddb_duplication.png" />
 &lt;/a>&lt;figcaption class="image-caption">메일이 하루라도 잘못오면 여기저기서 연락이 온다. 감사한 분들&amp;hellip;&lt;/figcaption>
 &lt;/figure>
&lt;p>이런저런 바쁜일들로 차일피일 미루다 마침 여유가 생겨 기존에는 Crontab 스케쥴로 파이썬 스크립트를 실행하던 것에서 Jenkins로 옮기는 작업을 했다. 젠킨스가 스케쥴링을 해주고 실행이력을 보여주며, 실시간으로 스크립트가 돌아가는걸 볼수 있을것 같다는 기대감에서이다. 위에서 이야기 했던 예외상황을 보다 빠르고 편하게 실시간으로 디버깅을 하기 위해서가 가장 컸다.&lt;/p>
&lt;h2 id="당연히-될거라고-생각했으나">당연히 될거라고 생각했으나&amp;hellip;&lt;/h2>
&lt;p>작업은 간단할꺼라 생각했다.&lt;/p>
&lt;ol>
&lt;li>우선 &lt;a href="https://taetaetae.github.io/2018/12/02/jenkins-install/" target="_blank" rel="noopener noreffer ">Jenkins를 설치&lt;/a>하고&lt;/li>
&lt;li>기존에 스크립트 파일을 Jenkins Job으로 옮긴후에&lt;/li>
&lt;li>적당한 코드 중간중간에 디버깅이 용이하도록 로그를 출력하게 해둔다음&lt;/li>
&lt;li>스케쥴링만 걸어두면 끝이라고 생각했다.&lt;/li>
&lt;/ol>
&lt;p>하지만, 이렇게 간단하게 끝날것만 같았던 작업이 은근 귀찮은 작업이 될줄이야. 디버깅을 위해 로그를 출력하도록 해놨는데 모든 스크립트가 끝이 나서야 해당 로그가 출력되는 것이였다. 로그를 실시간으로 볼수 없다면 Crontab에서 Jenkins로 옮기는 이유가 크게 없게 된다. 실제로 아래처럼 코드를 작성하고 Jenkins Job을 실행시켜보면 다 끝나고서야 출력이 되는걸 볼수 있었다.&lt;/p>
&lt;p>(1초에 한번씩 5초동안 로그를 찍는 간단한 코드다.)&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">time&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;start&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="n">second&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">second&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">second&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;end&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/python-buffer/before.gif" title="/images/python-buffer/before.gif" data-thumbnail="/images/python-buffer/before.gif" data-sub-html="&lt;h2>스크립트가 다 끝나서야 출력을 볼수 있다ㅠ 실시간으로 디버깅이 어렵다.&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/python-buffer/before.gif"
 data-srcset="https://taetaetae.github.io/images/python-buffer/before.gif, https://taetaetae.github.io/images/python-buffer/before.gif 1.5x, https://taetaetae.github.io/images/python-buffer/before.gif 2x"
 data-sizes="auto"
 alt="/images/python-buffer/before.gif" />
 &lt;/a>&lt;figcaption class="image-caption">스크립트가 다 끝나서야 출력을 볼수 있다ㅠ 실시간으로 디버깅이 어렵다.&lt;/figcaption>
 &lt;/figure>
&lt;h2 id="그럼-어떻게-해야할까">그럼 어떻게 해야할까?&lt;/h2>
&lt;p>개발을 하면서 만나는 대부분의 문제들은 누군가 과거에 경험했던 문제였고, 이미 해결된 문제일 확률이 상당히 높은것들이 많다. 이번에도 역시, 갓 스택 오버플로우 : &lt;a href="https://stackoverflow.com/questions/107705/disable-output-buffering" target="_blank" rel="noopener noreffer ">https://stackoverflow.com/questions/107705/disable-output-buffering&lt;/a>&lt;/p>
&lt;p>위 링크에서 알려준것처럼 해보면 다음과 같이 로그가 출력되는대로 젠킨스에서 볼수 있게 된다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/python-buffer/after.gif" title="/images/python-buffer/after.gif" data-thumbnail="/images/python-buffer/after.gif" data-sub-html="&lt;h2>콘솔환경에서의 디버깅은 로깅이 최고!&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/python-buffer/after.gif"
 data-srcset="https://taetaetae.github.io/images/python-buffer/after.gif, https://taetaetae.github.io/images/python-buffer/after.gif 1.5x, https://taetaetae.github.io/images/python-buffer/after.gif 2x"
 data-sizes="auto"
 alt="/images/python-buffer/after.gif" />
 &lt;/a>&lt;figcaption class="image-caption">콘솔환경에서의 디버깅은 로깅이 최고!&lt;/figcaption>
 &lt;/figure>
&lt;p>정리해보면 다음과 같은 방법이 있겠다.&lt;/p>
&lt;ol>
&lt;li>&lt;code>Execute Python script&lt;/code> 을 활용하여 Jenkins 에 직접 코드를 작성하는 경우&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>print의 flush옵션을 활용 ( &lt;a href="https://docs.python.org/3/library/functions.html?highlight=print#print" target="_blank" rel="noopener noreffer ">https://docs.python.org/3/library/functions.html?highlight=print#print&lt;/a> )&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;hello&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">flush&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>매번 print 가 될때마다 flush가 되도록 재정의&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">sys&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Unbuffered&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">object&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">stream&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stream&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">stream&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">write&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">data&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stream&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">write&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stream&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">flush&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">writelines&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">datas&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stream&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">writelines&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">datas&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stream&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">flush&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__getattr__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">attr&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nb">getattr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stream&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">attr&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stdout&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">Unbuffered&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stdout&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ol start="2">
&lt;li>&lt;code>Execute shell&lt;/code>을 활용하여 특정경로의 Python 파일을 실행할 경우&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>&lt;code>-u&lt;/code> 옵션을 줘서 실행시킨다. ( python -u python_module.py )&lt;/li>
&lt;/ul>
&lt;p>이렇게 두고보면 너무 간단한 작업인데 이런 방법을 모르는 상황에서는 작성된 Python Script를 Shell Script로 다시 감싸보거나 Python 코드를 쓰지 말까 까지 생각했었다&amp;hellip; 삽질의 연속들&amp;hellip; (Shell Script로 작성하면 바로바로 보였기 때문&amp;hellip;)&lt;/p>
&lt;p>다시한번 모르면 몸이 고생한다(?)라는걸 몸소 체험한 좋은&amp;hellip;시간이였다.&lt;/p></description></item><item><title>소나큐브 이용 코드 정적분석 자동화</title><link>https://taetaetae.github.io/2018/02/08/jenkins-sonar-github-integration/</link><pubDate>Thu, 08 Feb 2018 20:10:54 +0000</pubDate><guid>https://taetaetae.github.io/2018/02/08/jenkins-sonar-github-integration/</guid><description>&lt;p>&lt;code>코드 정적분석&lt;/code>이라 함은 실제 프로그램을 실행하지 않고 코드만의 형태에 대한 분석을 말한다. 이를테면 냄새나는 코드(?)라던지, 위험성이 있는 코드, 미리 정의된 규칙이나 코딩 표준을 준수하는지에 대한 분석을 말하는데 java 기준으로는 아래 다양한 (잘 알려진) 정적분석 도구들이 있다.&lt;!-- more -->&lt;/p>
&lt;ul>
&lt;li>PMD
&lt;ul>
&lt;li>미사용 변수, 비어있는 코드 블락, 불필요한 오브젝트 생성과 같은 Defect을 유발할 수 있는 코드를 검사&lt;/li>
&lt;li>&lt;a href="https://pmd.github.io" target="_blank" rel="noopener noreffer ">https://pmd.github.io&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>FindBugs
&lt;ul>
&lt;li>정해진 규칙에 의해 잠재적인 에러 타입을 찾아줌&lt;/li>
&lt;li>&lt;a href="http://findbugs.sourceforge.net" target="_blank" rel="noopener noreffer ">http://findbugs.sourceforge.net&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>CheckStyle
&lt;ul>
&lt;li>정해진 코딩 룰을 잘 따르고 있는지에 대한 분석&lt;/li>
&lt;li>&lt;a href="http://checkstyle.sourceforge.net" target="_blank" rel="noopener noreffer ">http://checkstyle.sourceforge.net&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>이외에 &lt;code>SonarQube&lt;/code> 라는 툴이 있는데 개인적으로 위 알려진 다른 툴들의 종합판(?)이라고 생각이 들었고, 그중 가장 인상깊었던 기능이 github과 연동이 되고 적절한 구성을 하게 되면 코드를 수정하는과 동시에 자동으로 분석을 하고 리포팅까지 해준다는 부분이였다. ( &lt;del>더 좋은 방법이 있는지는 모르겠으나&lt;/del> 다른 도구들은 수동으로 돌려줘야 하고 리포팅 또한 Active하지 못한(?) 아쉬운 점이 있었다. )&lt;/p>
&lt;p>지금부터 Jenkins + github web-hook + SonarQube 를 구성하여 코드를 수정하고 PullRequest를 올리게 되면 수정한 파일에 대해 자동으로 정적분석이 이뤄지고, 그에대한 리포팅이 해당 PullRequest에 댓글로 달리도록 설정을 해보겠다. (코드리뷰를 봇(?)이 자동으로 해주는게 얼마나 편한 일인가&amp;hellip;)&lt;/p>
&lt;h2 id="기본-컨셉">기본 컨셉&lt;/h2>
&lt;p>전체적인 컨셉은 다음 그림과 같다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/jenkins-sonar-github-integration/concept.png" title="/images/jenkins-sonar-github-integration/concept.png" data-thumbnail="/images/jenkins-sonar-github-integration/concept.png" data-sub-html="&lt;h2>전체 컨셉&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/jenkins-sonar-github-integration/concept.png"
 data-srcset="https://taetaetae.github.io/images/jenkins-sonar-github-integration/concept.png, https://taetaetae.github.io/images/jenkins-sonar-github-integration/concept.png 1.5x, https://taetaetae.github.io/images/jenkins-sonar-github-integration/concept.png 2x"
 data-sizes="auto"
 alt="/images/jenkins-sonar-github-integration/concept.png" />
 &lt;/a>&lt;figcaption class="image-caption">전체 컨셉&lt;/figcaption>
 &lt;/figure>
&lt;ol>
&lt;li>IDE에서 코드수정을 하고 remote 저장소에 commit &amp;amp; push를 한다.
그 다음 github에서 master(혹은 stable한 branch)에 대해 작업 branch를 PullRequest 올린다.&lt;/li>
&lt;li>미리 등록한 github의 web-hook에 의해 PullRequest 정보들을 jenkins에 전송한다.&lt;/li>
&lt;li>전달받은 정보를 재 가공하여 SonarQube로 정적분석을 요청한다.&lt;/li>
&lt;li>SonarQube에서 분석한 정보를 다시 jenkins로 return 해준다.&lt;/li>
&lt;li>SonarQube으로부터 return 받은 정보를 해당 PullRequest의 댓글에 리포팅을 해준다.&lt;/li>
&lt;/ol>
&lt;p>간단히 보면 (뭐 간단하니 쉽네~) 라고 볼수도 있겠지만 나는 이런 전체 흐름을 설정하는데 있어 어려웠다.&lt;/p>
&lt;blockquote>
&lt;p>사실 셋팅하는 과정에서 적지않은 삽질을 했었기에, 이 포스팅을 적는 이유일수도 있겠다.
더불어 검색을 해봐도 이렇게 전체흐름이 정리된 글이 잘 안보여서 + 내가 한 삽질을 다른 누군가도 할것같아서(?)&lt;/p>&lt;/blockquote>
&lt;h2 id="maven-설치">Maven 설치&lt;/h2>
&lt;p>기본적으로 Maven의 H2DB를 사용하므로 SonarQube를 설치하기전에 Maven부터 설치해줘야 한다.&lt;/p>
&lt;pre tabindex="0">&lt;code>$ wget http://apache.mirror.cdnetworks.com/maven/maven-3/3.5.2/binaries/apache-maven-3.5.2-bin.tar.gz
$ tar -zxvf apache-maven-3.5.2-bin.tar.gz
(환경변수 셋팅후 )
$ mvn -version
Apache Maven 3.5.2 (138edd61fd100ec658bfa2d307c43b76940a5d7d; 2017-10-18T16:58:13+09:00)
...
&lt;/code>&lt;/pre>&lt;h2 id="sonarqube-설치">SonarQube 설치&lt;/h2>
&lt;p>정적분석을 도와주는 SonarQube를 설치해보자.&lt;/p>
&lt;pre tabindex="0">&lt;code>$ wget https://sonarsource.bintray.com/Distribution/sonarqube/sonarqube-6.7.1.zip
$ unzip sonarqube-6.7.1.zip
$ cd sonarqube-6.7.1/bin/linux-x86-64
$ ./sonar.sh start
Starting SonarQube...
Started SonarQube.
&lt;/code>&lt;/pre>&lt;p>기본적으로 9000포트를 사용하고 있으니 다른포트를 사용하고자 한다면 /sonarqube-6.7.1/conf/sonar.properties 내 &lt;code>sonar.web.port=9000&lt;/code> 을 수정해주면 된다. (SonarQube도 Elasticsearch를 사용하구나&amp;hellip;)
설치후 실행을 한뒤 &lt;code>서버IP:9000&lt;/code>을 접속해보면 아래 화면처럼 나온다. (혹시 접속이 안된다거나 서버가 실행이 안된다면 &lt;code>./sonar.sh console&lt;/code>로 로그를 보면 문제해결에 도움이 될수도 있다. )&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/jenkins-sonar-github-integration/sonar_main.png" title="/images/jenkins-sonar-github-integration/sonar_main.png" data-thumbnail="/images/jenkins-sonar-github-integration/sonar_main.png" data-sub-html="&lt;h2>SonarQube 메인화면&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/jenkins-sonar-github-integration/sonar_main.png"
 data-srcset="https://taetaetae.github.io/images/jenkins-sonar-github-integration/sonar_main.png, https://taetaetae.github.io/images/jenkins-sonar-github-integration/sonar_main.png 1.5x, https://taetaetae.github.io/images/jenkins-sonar-github-integration/sonar_main.png 2x"
 data-sizes="auto"
 alt="/images/jenkins-sonar-github-integration/sonar_main.png" />
 &lt;/a>&lt;figcaption class="image-caption">SonarQube 메인화면&lt;/figcaption>
 &lt;/figure>
&lt;h2 id="sonarqube-scanner-설치">SonarQube Scanner 설치&lt;/h2>
&lt;p>소스를 연동시켜 정적분석을 하기 위해서는 SonarQube Scanner 라는게 필요하다고 한다. 아래 url에서 다운받아 적절한 곳에 압축을 풀어두자.
&lt;a href="https://docs.sonarqube.org/display/SCAN/Analyzing&amp;#43;with&amp;#43;SonarQube&amp;#43;Scanner" target="_blank" rel="noopener noreffer ">https://docs.sonarqube.org/display/SCAN/Analyzing+with+SonarQube+Scanner&lt;/a>&lt;/p>
&lt;pre tabindex="0">&lt;code>$ wget https://sonarsource.bintray.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-3.0.3.778-linux.zip
$ unzip sonar-scanner-cli-3.0.3.778-linux.zip
&lt;/code>&lt;/pre>&lt;h3 id="-jenkins-설치-및-sonarqube-연동"># jenkins 설치 및 SonarQube 연동&lt;/h3>
&lt;p>jenkins 설치는 간단하니 별도 언급은 안하고 넘어가&amp;hellip;려고 했으나, 하나부터 열까지 정리한다는 마음으로~
&lt;a href="https://jenkins.io/download/" target="_blank" rel="noopener noreffer ">https://jenkins.io/download/&lt;/a> 에서 최신버전을 tomcat/webapps/ 아래에 다운받고 server.xml 을 적절하게 수정해준다.&lt;/p>
&lt;pre tabindex="0">&lt;code>$ wget http://mirrors.jenkins.io/war-stable/latest/jenkins.war
$ vi tomcat/conf/server.xml
&amp;lt;Connector port=&amp;#34;19001&amp;#34; protocol=&amp;#34;HTTP/1.1&amp;#34; # 포트 변경
&amp;lt;Context path=&amp;#34;/jenkins&amp;#34; debug=&amp;#34;0&amp;#34; privileged=&amp;#34;true&amp;#34; docBase=&amp;#34;jenkins.war&amp;#34; /&amp;gt; #추가
# tomcat/bin/startup.sh
&lt;/code>&lt;/pre>&lt;p>jenkins 설치를 완료 한 후 필요한 플러그인을 추가로 설치해준다.&lt;/p>
&lt;ul>
&lt;li>Python Plugin&lt;/li>
&lt;li>GitHub Pull Request Builder&lt;/li>
&lt;li>GitHub plugin&lt;/li>
&lt;/ul>
&lt;p>접속 : &lt;code>서버IP:19001&lt;/code> (참고로 한 서버에서 다 설치하다보니 port 충돌을 신경쓰게되었다. )
처음 jenkins를 실행하면 이런저런 설정을 하는데 특별한 설정 변경없이 next버튼을 연신 눌러면 설치가 완료 되고, SonarQube를 사용하기 위해 &lt;code>SonarQube Scanner for Jenkins&lt;/code>라는 플러그인을 설치해주자. (이건 각 버전마다 궁합(?)이 안맞을수도 있으니 확인이 필요할수도 있다. 내가 설치한 버전은 jenkins 2.89, SonarQube Plugin 2.6.1이다.)
설치를 하면 jenkins &amp;gt; configure 에서 &lt;code>SonarQube servers&lt;/code>정보를 등록해준다.&lt;/p></description></item><item><title>Github의 WebHook을 이용하여 자동 Jenkins Job 실행</title><link>https://taetaetae.github.io/2018/02/08/github-web-hook-jenkins-job-excute/</link><pubDate>Thu, 08 Feb 2018 17:10:54 +0000</pubDate><guid>https://taetaetae.github.io/2018/02/08/github-web-hook-jenkins-job-excute/</guid><description>&lt;p>PullRequest가 발생하면 알림을 받고싶다거나, 내가 관리하는 레파지토리에 댓글이 달릴때마다 또는 이슈가 생성될때마다 정보를 저장하고 싶다거나. 종합해보면 &lt;code>Github에서 이벤트가 발생할때 어떤 동작을 해야 할 경우&lt;/code> Github에서 제공하는 Webhook 을 사용하여 목적을 달성할 수 있다.
아 당연한 이야기이지만 언급하고 넘어갈께 있다면, Github에서 Jenkins Job을 호출하기 위해서는 Jenkins가 외부에 공개되어 있어야 한다. (내부사설망이나 private 한 설정이 되어있다면 호출이 안되어 Webhook기능을 사용할 수 없다.)&lt;/p>
&lt;h2 id="jenkins-security-설정">Jenkins Security 설정&lt;/h2>
&lt;p>Jenkins Job을 외부에서 URL로 실행을 하기 위해서는 아래 설정이 꼭 필요하다. (이 설정을 몰라서 수많은 삽질을 했다.)
CSRF Protection 설정 체크를 풀어줘야 한다. 이렇게 되면 외부에서 Job에 대한 트리거링이 가능해 진다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/jenkins_config.png" title="/images/github-web-hook-jenkins-job-excute/jenkins_config.png" data-thumbnail="/images/github-web-hook-jenkins-job-excute/jenkins_config.png" data-sub-html="&lt;h2>Jenkins &amp;gt; Configure Global Security&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/jenkins_config.png"
 data-srcset="https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/jenkins_config.png, https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/jenkins_config.png 1.5x, https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/jenkins_config.png 2x"
 data-sizes="auto"
 alt="/images/github-web-hook-jenkins-job-excute/jenkins_config.png" />
 &lt;/a>&lt;figcaption class="image-caption">Jenkins &amp;gt; Configure Global Security&lt;/figcaption>
 &lt;/figure>
&lt;h2 id="jenkins-job-설정">Jenkins Job 설정&lt;/h2>
&lt;p>Github 에서 Webhook에 의해 Jenkins Job을 실행하게 될텐데, 그때 정보들이 &lt;code>payload&lt;/code>라는 파라미터와 함께 &lt;code>POST&lt;/code> 형식으로 호출이 되기 때문에 미리 Job에서 받는 준비(?)를 해둬야 한다.
설정은 간단하게 다음과 같이 Job 파라미터 설정을 해주면 된다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/jenkins_job_param.png" title="/images/github-web-hook-jenkins-job-excute/jenkins_job_param.png" data-thumbnail="/images/github-web-hook-jenkins-job-excute/jenkins_job_param.png" data-sub-html="&lt;h2>Jenkins &amp;gt; 해당 Job &amp;gt; configure&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/jenkins_job_param.png"
 data-srcset="https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/jenkins_job_param.png, https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/jenkins_job_param.png 1.5x, https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/jenkins_job_param.png 2x"
 data-sizes="auto"
 alt="/images/github-web-hook-jenkins-job-excute/jenkins_job_param.png" />
 &lt;/a>&lt;figcaption class="image-caption">Jenkins &amp;gt; 해당 Job &amp;gt; configure&lt;/figcaption>
 &lt;/figure>
&lt;h2 id="github-webhook-설정">Github Webhook 설정&lt;/h2>
&lt;p>이제 Github Repository 의 Hook 설정만 하면 끝이난다. 해당 Repository &amp;gt; Settings &amp;gt; Hooks 설정에 들어가서 &lt;code>Add webhook&lt;/code>을 선택하여 Webhook을 등록해준다.
URL은 &lt;code>{jenkins URL}/jenkins/job/{job name}/buildWithParameters&lt;/code>식으로 설정해주고 Content Type 은 &lt;code>application/x-www-form-urlencoded&lt;/code>으로 선택한다. 언제 Webhook을 트리거링 시킬꺼냐는 옵션에서는 원하는 설정에 맞추면 되겠지만 나는 &lt;code>pullRequest&lt;/code>가 등록 될때만 미리 만들어 놓은 Jenkins Job을 실행시킬 계획이였으니 &lt;code>Let me select individual events.&lt;/code>을 설정하고 &lt;code>Pull Request&lt;/code>에 체크를 해준다. 아래 그림처럼 말이다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/oss_webhook.png" title="/images/github-web-hook-jenkins-job-excute/oss_webhook.png" data-thumbnail="/images/github-web-hook-jenkins-job-excute/oss_webhook.png" data-sub-html="&lt;h2>해당 Repositroy &amp;gt; Settings &amp;gt; Hooks&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/oss_webhook.png"
 data-srcset="https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/oss_webhook.png, https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/oss_webhook.png 1.5x, https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/oss_webhook.png 2x"
 data-sizes="auto"
 alt="/images/github-web-hook-jenkins-job-excute/oss_webhook.png" />
 &lt;/a>&lt;figcaption class="image-caption">해당 Repositroy &amp;gt; Settings &amp;gt; Hooks&lt;/figcaption>
 &lt;/figure>
&lt;p>이렇게 등록하고 다시 들어가서 맨 아랫 부분&lt;code>Recent Deliveries&lt;/code>을 보면 ping test 가 이루어져 정상적으로 응답을 받은것을 확인할수가 있다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/oss_webhook_result.png" title="/images/github-web-hook-jenkins-job-excute/oss_webhook_result.png" data-thumbnail="/images/github-web-hook-jenkins-job-excute/oss_webhook_result.png" data-sub-html="&lt;h2>Webhook 등록 결과&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/oss_webhook_result.png"
 data-srcset="https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/oss_webhook_result.png, https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/oss_webhook_result.png 1.5x, https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/oss_webhook_result.png 2x"
 data-sizes="auto"
 alt="/images/github-web-hook-jenkins-job-excute/oss_webhook_result.png" />
 &lt;/a>&lt;figcaption class="image-caption">Webhook 등록 결과&lt;/figcaption>
 &lt;/figure>
&lt;p>이렇게 설정을 다 한 뒤 PullRequest를 발생시키면 Jenkins 해당 Job에서는 파라미터를 받으며 실행이 된것을 확인할수가 있다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/jenkins_webhook_result.png" title="/images/github-web-hook-jenkins-job-excute/jenkins_webhook_result.png" data-thumbnail="/images/github-web-hook-jenkins-job-excute/jenkins_webhook_result.png" data-sub-html="&lt;h2>Jenkins Job 실행 결과&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/jenkins_webhook_result.png"
 data-srcset="https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/jenkins_webhook_result.png, https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/jenkins_webhook_result.png 1.5x, https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/jenkins_webhook_result.png 2x"
 data-sizes="auto"
 alt="/images/github-web-hook-jenkins-job-excute/jenkins_webhook_result.png" />
 &lt;/a>&lt;figcaption class="image-caption">Jenkins Job 실행 결과&lt;/figcaption>
 &lt;/figure>
&lt;p>끝~&lt;/p></description></item><item><title>Github과 Jenkins 연동하기</title><link>https://taetaetae.github.io/2018/02/08/github-with-jenkins/</link><pubDate>Thu, 08 Feb 2018 17:10:21 +0000</pubDate><guid>https://taetaetae.github.io/2018/02/08/github-with-jenkins/</guid><description>&lt;p>Jenkins에서 Github의 소스를 가져와서 빌드를 하는 등 Github과 Jenkins와 연동을 시켜줘야하는 상황에서, 별도의 선행 작업이 필요하다. 다른 여러 방법이 있을수 있는데 여기서는 SSH로 연동하는 방법을 알아보고자 한다.&lt;!-- more -->&lt;/p>
&lt;p>우선 Jenkins가 설치되어있는 서버에서 인증키를 생성하자.&lt;/p>
&lt;pre tabindex="0">&lt;code>$ ssh-keygen -t rsa -f id_rsa
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in id_rsa.
Your public key has been saved in id_rsa.pub.
The key fingerprint is:
SHA256:~~~~~ ~~~@~~~~~
The key&amp;#39;s randomart image is:
+---[RSA 2048]----+
| o*+**=*=**+ |
| o B=o+o++o |
| E+.o+ + oo .|
| oo. * o ...|
| .+ S = o |
| . + o . |
| . . . |
| . |
| |
+----[SHA256]-----+

$ ls
id_rsa id_rsa.pub
&lt;/code>&lt;/pre>&lt;p>개인키(id_rsa)는 젠킨스에 설정해준다. (처음부터 끝까지 복사, 첫줄 마지막줄 빼면 안된다&amp;hellip; )&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/github-with-jenkins/ssh_jenkins.png" title="/images/github-with-jenkins/ssh_jenkins.png" data-thumbnail="/images/github-with-jenkins/ssh_jenkins.png" data-sub-html="&lt;h2>젠킨스에 SSH 개인키 설정&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/github-with-jenkins/ssh_jenkins.png"
 data-srcset="https://taetaetae.github.io/images/github-with-jenkins/ssh_jenkins.png, https://taetaetae.github.io/images/github-with-jenkins/ssh_jenkins.png 1.5x, https://taetaetae.github.io/images/github-with-jenkins/ssh_jenkins.png 2x"
 data-sizes="auto"
 alt="/images/github-with-jenkins/ssh_jenkins.png" />
 &lt;/a>&lt;figcaption class="image-caption">젠킨스에 SSH 개인키 설정&lt;/figcaption>
 &lt;/figure>
&lt;p>그 다음 공개키(id_rsa.pub)는 Github에 설정을 해준다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/github-with-jenkins/ssh_github.png" title="/images/github-with-jenkins/ssh_github.png" data-thumbnail="/images/github-with-jenkins/ssh_github.png" data-sub-html="&lt;h2>Github에 SSH 공개키 설정&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/github-with-jenkins/ssh_github.png"
 data-srcset="https://taetaetae.github.io/images/github-with-jenkins/ssh_github.png, https://taetaetae.github.io/images/github-with-jenkins/ssh_github.png 1.5x, https://taetaetae.github.io/images/github-with-jenkins/ssh_github.png 2x"
 data-sizes="auto"
 alt="/images/github-with-jenkins/ssh_github.png" />
 &lt;/a>&lt;figcaption class="image-caption">Github에 SSH 공개키 설정&lt;/figcaption>
 &lt;/figure>
&lt;p>이렇게 한뒤 Jenkins 에서 임의로 job을 생성하고 job 설정 &amp;gt; 소스코드 관리 에서 git 부분에 아래처럼 테스트를 해서 정상적으로 연동이 된것을 확인한다. &lt;code>Credentials&lt;/code> 값을 위에서 설정한 개인키로 설정하고, repo 주소를 SSH용으로 적었을때 에러가 안나오면 성공한것이다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/github-with-jenkins/ssh_complete.png" title="/images/github-with-jenkins/ssh_complete.png" data-thumbnail="/images/github-with-jenkins/ssh_complete.png" data-sub-html="&lt;h2>정상 연결되면 Jenkins 오류도 없고, github SSH 키에 녹색불이 들어온다.&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/github-with-jenkins/ssh_complete.png"
 data-srcset="https://taetaetae.github.io/images/github-with-jenkins/ssh_complete.png, https://taetaetae.github.io/images/github-with-jenkins/ssh_complete.png 1.5x, https://taetaetae.github.io/images/github-with-jenkins/ssh_complete.png 2x"
 data-sizes="auto"
 alt="/images/github-with-jenkins/ssh_complete.png" />
 &lt;/a>&lt;figcaption class="image-caption">정상 연결되면 Jenkins 오류도 없고, github SSH 키에 녹색불이 들어온다.&lt;/figcaption>
 &lt;/figure>
&lt;p>끝~&lt;/p></description></item></channel></rss>