<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Hugo on cublr's blueprint</title><link>https://b.cublr.com/tags/hugo/</link><description>Recent content in Hugo on cublr's blueprint</description><language>en</language><managingEditor>sparkstar@empas.com (sparkstar)</managingEditor><webMaster>sparkstar@empas.com (sparkstar)</webMaster><lastBuildDate>Sat, 22 Jan 2022 22:00:00 +0900</lastBuildDate><atom:link href="https://b.cublr.com/tags/hugo/index.xml" rel="self" type="application/rss+xml"/><item><title>해야만 하는 일이 있다</title><link>https://b.cublr.com/posts/%ED%95%B4%EC%95%BC%EB%A7%8C-%ED%95%98%EB%8A%94-%EC%9D%BC%EC%9D%B4-%EC%9E%88%EB%8B%A4/</link><pubDate>Sat, 22 Jan 2022 22:00:00 +0900</pubDate><author>sparkstar@empas.com (sparkstar)</author><guid>https://b.cublr.com/posts/%ED%95%B4%EC%95%BC%EB%A7%8C-%ED%95%98%EB%8A%94-%EC%9D%BC%EC%9D%B4-%EC%9E%88%EB%8B%A4/</guid><description>&lt;p&gt;어떤 일들은 반드시 일을 시작하기 전, 처음에 해야만 한다. 그런 일들은 만 명중 만 모두 동의하는 것이기도 하고, 또 모두가 동의하지 않아 우선순위에서 밀리는 것들이 있기 마련이다. 어느새 일을 시작한 날이 오래되어 많은 사람들과 일하면서 느낀 것이 있는데, 사람들이 생각하기에 중요한 일은 다 중요한 일이 아니고 대강 이렇게 두 가지가 있다고 믿는 것 같다. 중요하고 필수적인 것, 그리고 중요하지만 필수적이지는 않은 것. 그렇기 때문에 전자의 경우 처음에 진행하는 것에 아무런 이견을 달지 않지만, 다른 일은 반대하는 사람들이 생긴다. 여기서 포인트는 전자나 후자 모두 그것이 중요하다는 것에는 한결같이 동의한다는 것이다. 다만 각각이 생각하는 우선 순위가 다른 것. 회사일로 따지면 보통 중요한 것 중, 그중에서도 중요하다고 여기는 것들은 결과물을 낼 수 있는 프로그래밍 코드나 컴파일된 바이너리가 조금 더 그런 경향이 있는 것 같고, 그 이외의 것들, 예를 들어 배포 시스템이 중요하지만 우선순위가 낮다고 생각하는 것 같다.&lt;/p&gt;
&lt;p&gt;잠깐 다른 이야기를 하면, 나는 게임을 상당히 좋아하는 편이다. 운이 좋게도 어렸을 때 하루종일 많은 게임을 하더라도 집에서 딱히 혼내거나 하진 않아서 남들보다 더 많은 게임을 편하게 했다. 거기다 형제가 있어 게임 위 자연스러운 경쟁 관계를 가지게 되었다. 그런 환경에서 살았기 때문에, 지금은 무슨 게임을 하더라도 대부분의 사람들보다 빠르게 게임에 적응할 수 있게 되고 더 높은 실력을 낼 수 있게 되었다. 특히 사람과의 경쟁이 있는 게임은 특히 더 그러는 편인데, 이런 히스토리를 모르는 사람들은 이제, 게임에 재능이 있는거 아니냐, 집에서 아무것도 안하고 게임만 하는 것이 아니냐 하는 이야기도 종종 듣는다.&lt;/p&gt;
&lt;p&gt;게임을 잘 하기 위해서 제일 필요한 것은 무엇인가? 게임마다 그 목표는 다르지만 결국 달성해야 하는 것은 확실하다. 목적을 달성한다는 것이 바로 그것이다. 여러 장르의 게임이 있고 목적이 다 다르므로, &lt;code&gt;잘 한다&lt;/code&gt;의 기준이 다르니 여기서는 기준을 조금 좁혀서, 사람과 경쟁하는 것으로만 좁혀서 생각해 보자. 격투 게임이라면 내 캐릭터의 성능을 바르게 파악하는 것, 그래서 기술을 파악하고 상대보다 덜 맞고 더 때리는 것이 승리의 조건이다. 전략 시뮬레이션, 스타크래프트로 예를 들면 최적화된 빌드 오더를 실수없이 수행한다거나, 돌발 상황에서 유리함으로 이끄는 판단력 등이 필요하다. 그러나 이런 것들은 보통 나와 경쟁 상대가 이미 잘 하는 상황에서, 상대보다 더 필요한 능력이다. 서로 처음 시작했을 때, 완전한 제로에서 시작했다면 상대보다 더 필요한 것은 바로 능숙한 조작 능력이다.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://camo.githubusercontent.com/cf1ea3140830f90d414ee257e67396e0833337f3367b26535db3d2c6fa570554/68747470733a2f2f692e696d6775722e636f6d2f6c573377764b582e706e67" alt="hadoken"&gt;&lt;/p&gt;
&lt;p&gt;격투 게임에서는 버튼을 눌러 거기에 대응하는 기본기를 내는 것 이외에도, 방향키를 복잡하게 조작하여 특별한 기술을 낼 수 있다. 그리고 이런 기술들은 기본기보다 성능과 판정 모두 뛰어나서, 필요할 때 이런 기술을 쉽게 낼 수 있으면 대개 승리할 수 있다. 기본기로 닿지 않는 거리에서 상대를 견제하는 수단은 장풍이라고 불리는 파동권류 기술밖에 없고, 이를 통해 먼 거리의 상대 움직임에 제약을 줄 수 있는 것이다. 즉, 승리하기 위해서는 적절한 상황에 기술을 낼 수 있어야 한다. 적절한 상황에 기술을 낼 수 있어야 한다는 것, 이것은 일반적으로 &lt;code&gt;적절한 상황&lt;/code&gt;을 읽는 판단력을 이야기한다. 그러나 여기 이 글에서는 &lt;code&gt;기술을 낼 수 있어야 한다&lt;/code&gt;에 대한 이야기를 하려고 한다.&lt;/p&gt;
&lt;p&gt;기술의 조작은 쉬운 것부터 어려운 것까지 있으며, 보통 성능 좋은 것들은 복잡한 경향이 있는 편이다. 초보자들의 경우 이런 기술을 쓸 때의 과정이 대략 &lt;code&gt;기술을 써야겠군&lt;/code&gt;-&amp;gt;&lt;code&gt;방향키를 이렇게 돌리고&lt;/code&gt;-&amp;gt;&lt;code&gt;버튼을 눌러야겠다&lt;/code&gt;가 된다. 그래서 판단이 바로 기술로 이어지지 않는다. 거기에 추가로 그 발동 성공률에도 차이가 있다. 판단이 결과로 이어지지 않을 수도 있는 것이다. 일반적으로 잘 하는 사람들이란 &lt;code&gt;기술을 써야겠군&lt;/code&gt;이라는 판단과 기술의 발동이 동시에 이루어진다. 그리고 그 발동이 실패하지도 않는다. 55프레임을 모아야 나가는 소닉붐이 거의 55~56프레임 사이로 정확하게 발동한다. 즉 과정 자체가 아예 다르다.&lt;/p&gt;
&lt;p&gt;스타크래프트로 치면 유닛에 명령을 내리기 위해 마우스로 바닥이든 명령이든 뭔가를 클릭하는 것이 아니라, 적절한 단축키를 상황에 맞추어 낸다. 거기다 유닛, 명령 뿐 아니라 화면 지정 단축키까지도 사용한다. 판단의 과정이 &lt;code&gt;명령을 내려야겠군&lt;/code&gt;-&amp;gt;&lt;code&gt;유닛의 위치를 찾아서&lt;/code&gt;-&amp;gt;&lt;code&gt;공격 명령을 내려야겠다&lt;/code&gt; 가 아니라 &lt;code&gt;선택된 유닛이 여기로 공격을 가야겠다&lt;/code&gt;로 다른 것이다. 아직도 스타크래프트의 자원에는 미네랄과 가스만 있다고 생각하는 사람들이 많지만 이 게임에는 사실 시간이라는 자원도 있으며, 조작의 능숙함이 이 시간이라는 자원을 더 얻을 수 있게 만들어준다.&lt;/p&gt;
&lt;p&gt;게임을 잘 하기 위해 더 잘 때리고 더 잘 싸우는 다소 막연한 능력을 얻으려 노력하는 것보다(그리고 그런 것들은 보통 얻기도 힘든 능력이다), 달성하기 쉽고 연습한 만큼 빠르게 늘 수 있는 조작을 더 익숙하게 만드는 것이 더 중요하다는 뜻이다. 조작 능력이 익숙할수록 판단의 과정을 더 줄여줄 수 있고, 다른 &lt;code&gt;중요한 것에&lt;/code&gt; 더 집중할 수 있다. 그래서 조작은 우리가 생각하는 중요도보다 훨씬 더 중요하다. 반드시 익숙해지도록 훈련이 필요한 능력이다. 그리고 대부분의 사람은 움직일 수 있고 원하는 것을 할 수 있으면 조작에서 더이상 달성할 것이 없다고 생각하고 그것을 크게 중요하다고 여기지 않는다.&lt;/p&gt;
&lt;p&gt;연차가 많아지고 다닌 회사가 많아지면서, 그리고 작은 회사와 큰 회사, 그리고 국내에서 가장 큰 IT회사 등등&amp;hellip; 여러 회사에서 일하며 느낀 것은 가장 위에서 말한 그런 것이다. 누구나 중요하다고 생각하면서도 신기하게 우선 순위는 높다고 생각하지 않고, 다른 일들을 먼저 처리하는 것. 지나고 보면 더 급했다고 생각한 것들은 정말로 아무것도 아니었는데 말이다.&lt;/p&gt;
&lt;p&gt;기존의 미니 보드 odroid-u3에 구축된 쿠버네티스 시스템이 있었는데, 이번에 드디어 라즈베리 파이로 시스템을 다시 구축하였다. 그래서 기존 클러스터에 있던 job과 시스템을 마이그레이션 하는 중인데, 오늘 다소 장황한 이 글을 작성하는 이유는 여기에 구축된 시스템 때문이었다. 글을 많이 쓰고 있지는 않았지만 그래도 여기 블로그와 옆 위키에는 글을 작성하면 &lt;a href="https://b.cublr.com/posts/hugo-%EB%B0%B0%ED%8F%AC-%EA%B3%84%ED%9A%8D/"&gt;자동으로 빌드되어 배포가 되도록 구성해 놓은 적이 있다.&lt;/a&gt; 글을 작성하고 master 브랜치에 push하는 것으로 트리거가 발생하고, 이것이 job을 실행하도록 하여 블로그와 위키에 글을 자동 발행되도록 만든 배포 시스템인데, 이것을 만든 것이 2019년 말 쯤이었다. 그리고 이 시스템이 있었기 때문에 나는 판단의 과정, 여기서는 &lt;code&gt;발행의 과정&lt;/code&gt;을 줄일 수 있었다. &lt;code&gt;작성한 글을 발행해야지&lt;/code&gt;-&amp;gt;&lt;code&gt;작성한 글을 여기서 받아놓고&lt;/code&gt;-&amp;gt;&lt;code&gt;hugo로 빌드하고&lt;/code&gt;-&amp;gt;&lt;code&gt;결과를 이쪽에 복사하고&lt;/code&gt;-&amp;gt;&lt;code&gt;deployment를 업데이트해야겠다&lt;/code&gt;라는 복잡한 과정이, &lt;code&gt;master에 올려야지&lt;/code&gt;로 간단하게 줄어들었다. 필요한 것은 글을 발행하는 것이었고 그 과정은 줄었다. 그리고 그것이 2년 반 동안 문제없이 수행되었고, 여러가지 번거로운 과정이 있는지도 몰랐을 정도로 그 기간동안 충분하게 이득을 누렸다. 엄청나게 잘 구축할 필요도 없었던, 단순히 빌드 과정이 자동화된 정도로만 해 놓아도 그런 이득이 발생한 것이다. 그런 면에서 생각해 보면, 필요한 만큼만 시스템이 구축되어도 충분하게 이득이라는 것. 여기서 &lt;code&gt;필요한 만큼&lt;/code&gt;이란 단계를 하나로 줄이고 자동화한 것이며, 이것만 어떻게든 달성되어도 어마어마하게 잘 구축된 시스템과 동일한 이득을 누린다는 것이다.&lt;/p&gt;
&lt;p&gt;그래서 하고 싶은 말은, 프로젝트 시작 전 모두가 중요하다고 생각하면서도 그렇게 생각하지 않는 작업들이 있다면, 그리고 그것이 앞으로 진행될 개발에 자명한, 자명하지 않더라도 큰 이득을 줄 수 있겠다 판단하면, 사람들이 반대하더라도 필요한 그것을 구축하는 것이 좋다. 그리고 최대한 프로젝트 초반에 빨리 구축될수록 좋다. 그런 것이 만약 배포라면 판단의 과정, 발행의 과정처럼 그리고 &lt;code&gt;개발 사이클의 과정&lt;/code&gt;을 줄일 수 있다. 그리고 내 생각에&amp;hellip; 이런 시스템들은 대개 잘 구축되지 않아도 된다. 물론 잘 되면 좋지만 중요한 것은 달성해야 하는 목표, &lt;code&gt;필요한 단계를 줄여주는 것&lt;/code&gt;, 그것만 달성하면 되는 것이다. 잘 되었든 아니든간에 어차피 누릴 수 있는 이득은 동일하기 때문이다.&lt;/p&gt;</description></item><item><title>위키 업데이트</title><link>https://b.cublr.com/posts/update-wiki/</link><pubDate>Mon, 15 Jun 2020 01:08:50 +0900</pubDate><author>sparkstar@empas.com (sparkstar)</author><guid>https://b.cublr.com/posts/update-wiki/</guid><description>&lt;p&gt;&lt;a href="https://w.cublr.com"&gt;위키를 업데이트했다.&lt;/a&gt; 기존에는 &lt;a href="https://www.dokuwiki.org"&gt;Dokuwiki&lt;/a&gt; 기반의 사이트였는데, 이번에 블로그와 같이 &lt;a href="https://gohugo.io/"&gt;hugo&lt;/a&gt;를 사용하기로 했다. 도쿠위키, 생각해보면 도쿠위키가 진짜 애매한 것이 아닌가 싶다. 파일 기반으로 위키를 관리하면서 정적 페이지도 아니고, 그렇다고 뭔가 현란한 기능이 있는 것도 아니고.&lt;/p&gt;
&lt;p&gt;처음에 위키를 만들 때 어떤 솔루션으로 할지 신중하게 고민했던 기억이 있다. 그러면서 어떤 것이 장점이 되고 단점이 될지 여러 위키 엔진을 비교하며 결정을 했는데, 그 때 도쿠위키를 선택한 이유는 크게 이런 이유들이었다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;파일 기반이라 데이터베이스가 필요없다&lt;/li&gt;
&lt;li&gt;&lt;code&gt;php&lt;/code&gt;기반이므로 잡다한 권한 관리가 될 것이다&lt;/li&gt;
&lt;li&gt;&amp;hellip;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;블로그를 &lt;code&gt;hugo&lt;/code&gt;로 변경하니, 위키도 정적 페이지로 만들어볼만 하겠다는 생각이 들었다. 파일 기반으로 위키를 만들 것이면서 굳이 php를 사용할 필요가 있나 싶은 것이다. 데이터베이스에 접근하는 것도 아니고, 그렇다고 뭔가 리치 텍스트나 복잡한 내용을 쓰는 것도 아니고. 거기다 플러그인을 통해 작성도 마크다운으로 했으니까, 생각해 보면 굳이 사용할 필요가 없었던 것. 또 복잡한 권한 관리가 가능하지 않을까 싶었는데, 애초에 나에게는 권한 관리가 필요 없었던 것이다.혼자 작성하는데.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;hugo&lt;/code&gt;에서 제시하는 여러 설치 가능한 테마들은 주로 블로그 느낌이 나는 테마들이 많았다. 어떤 것을 선택해서 수정하면 위키처럼 보이게 할 수 있을까 싶어 이런저런 것들을 살펴보다 보니 &lt;a href="https://themes.gohugo.io/hugo-theme-m10c/"&gt;m10c&lt;/a&gt;라는 테마를 선택했다. 일단 어두운 테마가 마음에 들었고, 적당히 심플해서 느낌이 좋다.&lt;/p&gt;
&lt;p&gt;&lt;img src="images/m10c.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;이 테마는 왼쪽의 사진 등이 들어가는 공간이 나에게는 전혀 필요가 없었으므로, 이런저런 시도를 해 보며 &lt;code&gt;hugo&lt;/code&gt;에 대한 학습도 병행했는데, 이게 생각보다 꽤 깊이가 있고, 고민한 흔적이나 다른 느낌이 매우 괜찮아서 마음에 든다. 정적 페이지를 만들어주는 다른 툴을 써보지는 않아서 비교는 못하겠지만, 생각보다 매우 괜찮지 않나? 하는 생각이 든다.&lt;/p&gt;
&lt;p&gt;이를테면 오버라이드를 통해 원 테마를 변경하지 않고도 변경점을 만든다거나, &lt;code&gt;partial&lt;/code&gt; 템플릿을 통해 통해 테마를 덮어씌울 수 있는 부분 등이 마음에 들었다. 특히 문서도 잘 되어있어서 오버라이드의 순서라거나 적당한 예제 등만 확인해도 가볍게 코드를 수정할 수 있었다는 말이다. 거기다 &lt;a href="https://golang.org/pkg/text/template/"&gt;go-template&lt;/a&gt;을 통해 여러 변수를 사용하여 처리하는 부분도 꽤 마음에 들었다. 물론 go-template 자체를 별로 좋아하지는 않지만.&lt;/p&gt;
&lt;p&gt;&lt;img src="images/wiki.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;덕분에 기존 블로그 시스템 배포 이외에도 위키 시스템의 배포를 위해 이런저런 코드 수정이 있었으므로, 이렇게 이번 주말은 위키 업데이트를 위해 사용했다.&lt;/p&gt;</description></item><item><title>Hugo 배포 계획</title><link>https://b.cublr.com/posts/hugo-%EB%B0%B0%ED%8F%AC-%EA%B3%84%ED%9A%8D/</link><pubDate>Sat, 01 Feb 2020 01:06:11 +0900</pubDate><author>sparkstar@empas.com (sparkstar)</author><guid>https://b.cublr.com/posts/hugo-%EB%B0%B0%ED%8F%AC-%EA%B3%84%ED%9A%8D/</guid><description>&lt;p&gt;기존에는 tumblr를 사용해서 블로그를 띄웠었는데, 자체 쿠버네티스도 구축되어 있으므로 이제부터는 &lt;a href="https://gohugo.io/"&gt;&lt;code&gt;Hugo&lt;/code&gt;&lt;/a&gt;를 사용해서 해 보려고 한다. 그래서 hugo에 대한 이런저런 사용법을 확인해보고 있는데&amp;hellip; hugo&amp;hellip; 꽤 괜찮은 느낌이다. 어차피 변하지 않는 컨텐츠를 배포하려는 것이므로 뭔가 복잡하게 배우거나 그럴 필요도 없이 적당히 &lt;code&gt;Markdown&lt;/code&gt;으로 작성하면 html이 출력된다니 이 얼마나 간편한가.&lt;/p&gt;
&lt;p&gt;그럼에도 불구하고 사용법을 보며 언뜻 떠오르지 않는 것들이 있었다. 과역 어떻게 컨텐츠를 배포하는 것이 효율적일까? 하는 것이 바로 그것들 중 하나가 되겠다. 글쓰기 폼이 따로 있는 것이 아니니 어딘가 다른 곳에서 글을 작성해야 하는 것이고, 글이 어느 정도 써지면 이게 자동으로 배포가 되었으면 하는 것이다. 글이 실제로 반영되려면 hugo를 통한 정적 컨텐츠로의 빌드 과정이 포함되어야 하는데, 이걸 누가 트리거링 해줄 것인가도 문제가 된다.&lt;/p&gt;
&lt;p&gt;이 블로그는 휴고로 생성한 템플릿인데 이 템플릿 전체가 git에 통째로 올라갈 것이다. git에 커밋이 새로 추가되는 경우 정적 컨텐츠를 새로 빌드하여야 하는 신호로 생각하고 컨텐츠를 새로 빌드할 것이다. 그렇다면 구성을 어떻게 해야 할까? 우선 트리거링을 주는 것은 &lt;code&gt;git webhook&lt;/code&gt;을 사용하면 충분히 가능할 것 같은데&amp;hellip;&lt;/p&gt;
&lt;p&gt;그래서 필요한 것들을 한번 순서대로 생각해 보았다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;hugo로 빌드된 정적 컨텐츠를 보여줄 수 있는 서버가 필요하다.&lt;/li&gt;
&lt;li&gt;git 저장소에 저정된 hugo 데이터를 빌드하는 서버가 필요하다.&lt;/li&gt;
&lt;li&gt;git의 데이터가 업데이트되었다면 &lt;code&gt;git webhook&lt;/code&gt;을 사용해 어딘가에 알려야 한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git webhook&lt;/code&gt;을 받아 뭔가를 실행할 수 있는 서버가 필요하다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;먼저 1번, 정적 컨텐츠를 보여주는 것은 &lt;code&gt;nginx&lt;/code&gt;로 비롯되는 서버들로 간단히 처리할 수 있다. 사용중인 &lt;code&gt;git&lt;/code&gt;서버도 있으므로 &lt;code&gt;webhook&lt;/code&gt;이벤트를 어딘가로 던지는 것도 문제는 없다. 문제는 webhook 이벤트를 받는 대몬이 없다는 것이다. 그렇다고 이걸 막 또 만들기도 그렇고. 그래도 조금 찾아보니 이런 상황에 간단하게 사용할 수 있도록 &lt;a href="https://github.com/adnanh/webhook"&gt;webhook&lt;/a&gt;이라는 것이 있다는 것을 알게 되었다. 다양한 웹훅 이벤트에 대한 처리를 하기 위해서 이런저런 정의를 &lt;code&gt;json&lt;/code&gt;으로 정의할 수 있는데, 이게 상세한 &lt;a href="https://github.com/adnanh/webhook/blob/master/docs/Hook-Examples.md"&gt;hook-example&lt;/a&gt; 예제가 있어서 여러 상황에서 적당히 맞추어가면서 쓸 수 있지 않을까? 마지막으로 필요한 것이 있다면 webhook 이벤트를 받은 후 빌드 명령을 날릴 스크립트나 코드가 필요한데, 이건 어쩔 수 없이 코드를 작성하여야 하겠다&amp;hellip;&lt;/p&gt;
&lt;p&gt;따라서 쿠버네티스 위에서 돌린다고 했을 때 다음과 같은 그림이 나온다.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://docs.google.com/drawings/d/e/2PACX-1vTNvjxyld7qz-rV-JtKAWfqRuBETpwWmfvjphwbwb6UP0B2aXfYLk3wz5G-aBqTb4l1xFbGpHCySilc/pub?w=828&amp;amp;h=362" alt="kubernetes-architecture"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;유저가 글을 쓴 후 git에 해당 내용을 업로드한다.&lt;/li&gt;
&lt;li&gt;git에서 발생한 webhook이 &lt;code&gt;webhook&lt;/code&gt; 서버에 이벤트를 보낸다.&lt;/li&gt;
&lt;li&gt;받은 이벤트를 통해 git 데이터를 다운로드한 후 정적 데이터로 빌드한다.&lt;/li&gt;
&lt;li&gt;빌드된 데이터는 PVC에 저장한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;뭐 이렇다.&lt;/p&gt;
&lt;h2 id="webhook-설정"&gt;webhook 설정&lt;/h2&gt;
&lt;p&gt;Webhook은 앞에서 말한 대로 &lt;a href="https://github.com/adnanh/webhook"&gt;webhook&lt;/a&gt;을 사용했다. go로 작성된 간단한 대몬이고, &lt;code&gt;-hooks&lt;/code&gt; 옵션으로 웹훅 설정 파일을 읽어 해당 조건에 맞는 웹훅을 발생시킨다. 웹훅 이벤트를 보내는 주체는 이미 구축된 &lt;a href="https://gogs.io/"&gt;Gogs&lt;/a&gt;인데, 설정이 매우 간단했다. 웹훅 이벤트 발생시 들어오는 이벤트 정보는 &lt;a href="https://gogs.io/docs/features/webhook"&gt;Gogs의 가이드에서&lt;/a&gt; 확인할 수 있다. 이 이벤트를 보고 webhook의 설정을 만들면 되는데, 다음과 같은 식으로 설정하여 이를 쿠버네티스의 &lt;code&gt;ConfigMap&lt;/code&gt;으로 작성하였다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;Name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;hook-information&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;Namespace&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;hook-namespace&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;Labels&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;&amp;lt;none&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;Annotations&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;&amp;lt;none&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;Data&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;====&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;hooks.json&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;----
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;id&amp;#34;: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;update-blogs&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;execute-command&amp;#34;: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;/mnt/builder&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;command-working-directory&amp;#34;: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;/mnt&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;trigger-rule&amp;#34;: &lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;and&amp;#34;: &lt;/span&gt;[
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;match&amp;#34;: &lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;type&amp;#34;: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;payload-hash-sha256&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;secret&amp;#34;: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;SECRET_XXXXXX&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;parameter&amp;#34;: &lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;source&amp;#34;: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;header&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;name&amp;#34;: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;X-Gogs-Signature&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;match&amp;#34;: &lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;type&amp;#34;: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;value&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;value&amp;#34;: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;refs/heads/master&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;parameter&amp;#34;: &lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;source&amp;#34;: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;payload&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;name&amp;#34;: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;ref&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;Events&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;&amp;lt;none&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;&amp;lt;Paste&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;다음과 같은 조건을 노리고 이를 작성했는데,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;X-Gogs-Signature&lt;/code&gt;로 간단한 인증 절차를 밟도록 하고,&lt;/li&gt;
&lt;li&gt;브랜치 &lt;code&gt;master&lt;/code&gt;에 새로운 커밋이 발생한다면&lt;/li&gt;
&lt;li&gt;이미 준비된 &lt;code&gt;/mnt/builder&lt;/code&gt;를 실행시킨다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이런 식으로 동작한다. webhook이 동작하는 서버에서는 &lt;code&gt;/mnt/builder&lt;/code&gt;를 실행해서, 직접 hugo를 사용해 컨텐츠를 빌드하지 않고 정적 컨텐츠를 빌드하는 쿠버네티스의 &lt;code&gt;Jobs&lt;/code&gt;를 생성하도록 하였다. 굳이 이유를 정하자면 각 서버가 하는 역할을 분리하고 싶어서였다. 특히나 &lt;code&gt;Jobs&lt;/code&gt;가 그런 용도에 더 적합해 보이기도 했고. 하여간 &lt;code&gt;Jobs&lt;/code&gt;를 생성하는 코드는 &lt;code&gt;go&lt;/code&gt;와 &lt;code&gt;client-go&lt;/code&gt;라이브러리를 를 사용해서 매우 간단하게 처리했다.&lt;/p&gt;
&lt;h2 id="jobs-생성하기"&gt;Jobs 생성하기&lt;/h2&gt;
&lt;p&gt;정적 컨텐츠를 빌드하는 &lt;code&gt;Jobs&lt;/code&gt;는 다음의 역할을 필요로 한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;git 저장소로부터 최신 컨텐츠를 가져온다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hugo&lt;/code&gt;를 사용하여 컨텐츠를 빌드한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;단순히 생각해도 두 가지 작업을 해야 한다. &lt;code&gt;Pod&lt;/code&gt;에서 두 가지 작업을 하게 되었으므로 이를 컨테이너 두 개로 나눠야 한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;initContainers: git 저장소로부터 데이터를 &lt;code&gt;git clone&lt;/code&gt;한다. 이 때 서브모듈로 등록된 테마 등을 같이 가져온다.&lt;/li&gt;
&lt;li&gt;containers: &lt;code&gt;hugo --source CLONE_DIRECTORY --dest VOLUME&lt;/code&gt;을 실행한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이렇게 작성하면 될 것 같다. 그래서 이와 똑같이 동작하는 go 코드를 작성했다. 이 코드의 결과물은 위에 작성한 webhook 팟에서 동작하게 되는데, 권한을 위해 쿠버네티스의 &lt;code&gt;ServiceAccount&lt;/code&gt;, &lt;code&gt;Role&lt;/code&gt;, &lt;code&gt;RoleBinding&lt;/code&gt;을 함께 작성하였다. &lt;code&gt;Role&lt;/code&gt;에는 간단하게 &lt;code&gt;Jobs&lt;/code&gt;를 생성만 가능한 권한을 부여했는데, 다음과 같이 작성했다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;rbac.authorization.k8s.io/v1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Role&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;metadata&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;namespace&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;hook-namespace&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;batch&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;rules&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- &lt;span style="color:#f92672"&gt;apiGroups&lt;/span&gt;: [&lt;span style="color:#e6db74"&gt;&amp;#34;batch&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;resources&lt;/span&gt;: [&lt;span style="color:#e6db74"&gt;&amp;#34;jobs&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;verbs&lt;/span&gt;: [&lt;span style="color:#e6db74"&gt;&amp;#34;create&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;이 &lt;code&gt;Role&lt;/code&gt;을 &lt;code&gt;RoleBinding&lt;/code&gt;을 사용하여 &lt;code&gt;ServiceAccount&lt;/code&gt;와 엮은 후 webhook 팟에 &lt;code&gt;ServiceAccount&lt;/code&gt;를 제공한다. 이제 webhook 컨테이너에서 &lt;code&gt;client-go&lt;/code&gt;의 &lt;a href="https://godoc.org/k8s.io/client-go/rest#InClusterConfig"&gt;&lt;code&gt;rest.InClusterConfig()&lt;/code&gt;&lt;/a&gt;를 호출하면 해당 권한을 가진 쿠버네티스 클라이언트를 사용할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://docs.google.com/drawings/d/e/2PACX-1vRkHJShoT0ePZ5GH77iQG2DFe4pARLba3s2WGrGIJTGhq-39Pbz92a16sAR55q95CkzW0RhsbquqVvQ/pub?w=1003&amp;amp;h=712" alt="rolebinding"&gt;&lt;/p&gt;
&lt;h2 id="문제점"&gt;문제점&lt;/h2&gt;
&lt;p&gt;이렇게 하면 이제 원하는 대로 동작하게 된다. 새로운 브랜치를 생성해서 글을 쓰다가 다 작성한 후 master에 머지하여 이를 커밋한다면 webhook 이벤트가 발생하고, 이 이벤트를 받은 webhook 대몬은 &lt;code&gt;Jobs&lt;/code&gt;리소스를 생성하게 될 것이고, &lt;code&gt;Jobs&lt;/code&gt;가 정적 컨텐츠를 빌드한다. 하지만 항상 원하는 대로 되는 것은 아니니&amp;hellip;&lt;/p&gt;
&lt;h3 id="nfs-chtimes-permission-denied"&gt;nfs chtimes permission denied&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl logs -f hugo-static-builder
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Building sites … Total in &lt;span style="color:#ae81ff"&gt;1231&lt;/span&gt; ms
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Error: Error copying static files: chtimes /mnt/css/style.css: permission denied
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;한 번씩 빌드가 끝난 후 &lt;code&gt;PersistentVolume&lt;/code&gt;으로 결과를 복사하질 못한다. 동시 쓰기 문제등으로 인해 발생하는 문제로 추정되는데 같은 볼륨을 사용하지 않도록 해야 하나 고민중이다.&lt;/p&gt;
&lt;h3 id="jobs-히스토리-관리"&gt;Jobs 히스토리 관리&lt;/h3&gt;
&lt;p&gt;쿠버네티스 &lt;code&gt;CronJob&lt;/code&gt;의 경우 &lt;code&gt;Jobs&lt;/code&gt;의 히스토리 관리가 가능한데, &lt;code&gt;Jobs&lt;/code&gt; 자체는 그런 관리가 불가능한 것 같다. 완료된 잡이 히스토리에 남아 새로운 잡을 실행할 경우 동일한 이름의 잡이 있으므로 새로 생성되지 않는다. 우선 &lt;code&gt;GenerateName&lt;/code&gt;을 사용해서 랜덤한 이름을 부여하도록 하여 이를 우회했는데, 더 간단한 방법이 있을지 찾아봐야 할 것 같다.&lt;/p&gt;
&lt;h2 id="이후-할-일"&gt;이후 할 일&lt;/h2&gt;
&lt;p&gt;데이터를 백업하는 방법도 강구해야 할 것 같다. &lt;a href="%60https://github.com/andreafabrizi/Dropbox-Uploader%60"&gt;dropbox-uploader&lt;/a&gt;를 사용해서 간단히 처리할 수 있을 것이다. &lt;code&gt;initContainer&lt;/code&gt;를 통해 빌드 전에 처리하도록 해야겠다.&lt;/p&gt;</description></item></channel></rss>