<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Kubernetes on cublr's blueprint</title><link>https://b.cublr.com/tags/kubernetes/</link><description>Recent content in Kubernetes on cublr's blueprint</description><language>en</language><managingEditor>sparkstar@empas.com (sparkstar)</managingEditor><webMaster>sparkstar@empas.com (sparkstar)</webMaster><lastBuildDate>Sun, 05 Jun 2022 23:59:59 +0900</lastBuildDate><atom:link href="https://b.cublr.com/tags/kubernetes/index.xml" rel="self" type="application/rss+xml"/><item><title>Kubernetes에서 AWS ECR 인증 정보를 자동 갱신하기</title><link>https://b.cublr.com/posts/kubernetes%EC%97%90%EC%84%9C-aws-ecr-%EC%9D%B8%EC%A6%9D-%EC%A0%95%EB%B3%B4%EB%A5%BC-%EC%9E%90%EB%8F%99-%EA%B0%B1%EC%8B%A0%ED%95%98%EA%B8%B0/</link><pubDate>Sun, 05 Jun 2022 23:59:59 +0900</pubDate><author>sparkstar@empas.com (sparkstar)</author><guid>https://b.cublr.com/posts/kubernetes%EC%97%90%EC%84%9C-aws-ecr-%EC%9D%B8%EC%A6%9D-%EC%A0%95%EB%B3%B4%EB%A5%BC-%EC%9E%90%EB%8F%99-%EA%B0%B1%EC%8B%A0%ED%95%98%EA%B8%B0/</guid><description>&lt;h2 id="aws-ecr이-불편한-점"&gt;AWS ECR이 불편한 점&lt;/h2&gt;
&lt;p&gt;&lt;img src="images/aws-ecr.png" alt="ecr"&gt;&lt;/p&gt;
&lt;p&gt;도커 이미지를 저장하기 위해 여러 레지스트리를 사용중이다. &lt;a href="https://hub.docker.com/"&gt;docker hub&lt;/a&gt; 도 사용하지만 &lt;a href="https://www.docker.com/pricing/"&gt;private저장소는 일반 유저에게 한 개만 주므로&lt;/a&gt; 편안히 사용하기가 쉽지 않다. 그래서 최근에는 직접 구축해서 사용할까 했지만, 구축하는 것도 일이고 저장소에 문제가 생기면 그건 또 그것대로 골치아프므로 그냥 &lt;a href="https://aws.amazon.com/ko/ecr/"&gt;AWS ECR&lt;/a&gt;을 더 적극적으로 사용하기로 했다.&lt;/p&gt;
&lt;p&gt;그동안 ECR을 사용하지 않았던 이유는 별 건 아니고, ECR의 인증이 꽤 번거롭기 때문이다. AWS CLI와 억세스/시크릿 키를 사용하여 도커 저장소의 인증 정보를 가져와야 하고, 이를 통해 docker에 로그인해야 한다. &lt;a href="https://docs.aws.amazon.com/AmazonECR/latest/userguide/registry_auth.html"&gt;더구나 이 인증 정보는 12시간 후에는 더이상 사용할 수 없게 되므로&lt;/a&gt; 이 작업을 매번 반복해야 하는 것이다. 이게 이럴리가 없다고 생각했는데 아무리 찾아도 안나오는게 어떻게 된건지 도무지 이해가 가지 않는다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;An authorization token&amp;rsquo;s permission scope matches that of the IAM principal used to retrieve the authentication token. An authentication token is used to access any Amazon ECR registry that your IAM principal has access to and is valid for 12 hours. To obtain an authorization token, you must use the GetAuthorizationToken API operation to retrieve a base64-encoded authorization token containing the username AWS and an encoded password. The AWS CLI get-login-password command simplifies this by retrieving and decoding the authorization token which you can then pipe into a docker login command to authenticate.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;빌드시에는 이런 과정에 인증에 관련한 큰 문제가 없다. 개인적으로 사용중인, 새롭게 구축된 파이프라인은 &lt;a href="https://tekton.dev/"&gt;tekton&lt;/a&gt; + &lt;a href="https://argoproj.github.io/cd/"&gt;argocd&lt;/a&gt; 기반인데 tekton에는 &lt;a href="https://hub.tekton.dev/tekton/task/aws-ecr-login"&gt;ECR 인증 처리를 해주는 태스크가 있어서&lt;/a&gt; 매 빌드 시에 이를 사용해서 갱신해 주면 되기 때문이다. 문제는 앱이 급작스럽게 죽었을 때다. 어플리케이션이 재시작을 해야 하므로 이미지를 다시 다운로드하는데, 이 때 레지스트리에 접근하지 못하면 어플리케이션이 다시 배포되지 못한다. 결함에서 장애로 등급이 올라가는 것이다. 따라서 이를 피하기 위해서는 ECR로의 인증 상태가 항상 유지되어야 한다.&lt;/p&gt;
&lt;h2 id="ecr-인증-정보를-자동-갱신하기"&gt;ECR 인증 정보를 자동 갱신하기&lt;/h2&gt;
&lt;p&gt;인증 상태를 매번 유지하기 위해서는 인증된 키가 만료되기 전에 다시 인증을 수행하고 이를 Kubernetes의 &lt;code&gt;Secret&lt;/code&gt;에 업데이트하면 된다. 만료는 12시간이므로 대략 6시간에 한 번씩 갱신하면, 어플리케이션이 자동 배포될 때 절대로 인증이 만료된 상태를 접할 일이 없을 것이다. 6시간에 한 번씩 갱신하기 위해 어떤 방법을 쓸까 하다가, Kubernetes의 &lt;a href="https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/"&gt;CronJob&lt;/a&gt; 을 사용하기로 했다.&lt;/p&gt;
&lt;p&gt;CronJob은 Job을 Cron에 맞추어 자동 실행하도록 하는 컨트롤러다. 이를 사용하면 리눅스에서 그냥 cron을 등록하듯 자동으로 팟이 실행되는데, 이제 이 팟은 kubernetes 안에서 실행되어 자신이 속해 있는 클러스터의 secret을 업데이트하도록 동작하게 될 것이다.&lt;/p&gt;
&lt;p&gt;팟에는 Secret을 조작할 수 있는 권한이 있어야 한다. 팟의 권한은 그 팟이 사용하는 &lt;code&gt;ServiceAccount&lt;/code&gt;에 의한다. ServiceAccount에 부여된 권한이 곧 팟이 할 수 있는 일이 된다. 그런 ServiceAccount에 직접 권한을 부여할 수는 없고, 어떤 권한과 ServiceAccount를 하나로 묶는 어떤 리소스를 통해 이를 부여할 수 있는데, 권한은 &lt;code&gt;Role&lt;/code&gt;이 되고 묶는 역할을 하는 리소스는 &lt;code&gt;RoleBinding&lt;/code&gt;이 된다. 따라서 &lt;code&gt;Role&lt;/code&gt;과 &lt;code&gt;ServiceAccount&lt;/code&gt;를 만들고 이를 &lt;code&gt;RoleBinding&lt;/code&gt;을 통해 연결하면 된다.&lt;/p&gt;
&lt;p&gt;&lt;img src="images/RBAC.png" alt="rbac"&gt;&lt;a href="https://www.cncf.io/blog/2020/08/28/kubernetes-rbac-101-authorization/"&gt;https://www.cncf.io/blog/2020/08/28/kubernetes-rbac-101-authorization/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;AWS ECR의 인증 정보는 &lt;a href="https://aws.amazon.com/ko/cli/"&gt;AWS CLI&lt;/a&gt;의 &lt;code&gt;ecr get-login-password&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-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ aws ecr get-login-password --region ap-northeast-2
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;emdrcWhraUc5dzBCQndhZ2J6QnRl...YUtUc3FUQV9LRVkiLCJleHBpcmF0aW9uIjoxNjU0NDcwNDIxfQ&lt;span style="color:#f92672"&gt;==&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;여기서 나오는 출력을 쉘 파이프를 통해 &lt;code&gt;docker login&lt;/code&gt;으로 보내면 docker에서도 사용할 수 있다. 이 cli를 실행할 때는 반드시 AWS의 &lt;code&gt;ACCESS_KEY_ID&lt;/code&gt;와 &lt;code&gt;SECRET_ACCESS_KEY&lt;/code&gt;가 필요하다. 이는 설정 파일로 cli에 제공할 수 있고 환경 변수로도 전달하는 것이 가능한데, 환경 변수를 제공한다면 kubernetes의 &lt;code&gt;configmap&lt;/code&gt;이나 &lt;code&gt;secret&lt;/code&gt;을 고려할 수 있다. 키 정보는 굉장히 민감한 정보이므로 여기서는 configmap이 아닌 secret으로 만들기로 했다.&lt;/p&gt;
&lt;p&gt;따라서 필요한 리소스를 정리해 보면 다음과 같다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;cronjob: 주기적으로 ECR에 로그인하여 새로운 키 정보를 받는다.&lt;/li&gt;
&lt;li&gt;serviceaccount, role, rolebinding: cronjob에서 만든 job이 스스로 secret을 업데이트할 수 있도록 권한을 부여한다.&lt;/li&gt;
&lt;li&gt;secret: secret(type opaque)이며 AWS cli를 사용할 수 있도록 access/secret 정보를 저장한다.&lt;/li&gt;
&lt;li&gt;imagepullsecret: secret(type dockerconfigjson)이며, 이를 imagePullSecret으로 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://docs.google.com/drawings/d/e/2PACX-1vSEW2RYvpw8oBNM8Sw0dwSYYUHf7EYSt6Wf7GZtgPbuF7eJBzT-pTJmrXhRhRp2t5TMt8VJ1h86N-1b/pub?w=945&amp;amp;h=594" alt="image"&gt;
사실 이미지는 그리지 않아도 됐는데 심심해서 한번 그려 봤다.&lt;/p&gt;
&lt;h2 id="차트-작성하기"&gt;차트 작성하기&lt;/h2&gt;
&lt;p&gt;계획대로 그냥 각 리소스를 yaml로 직접 작성해서 배포해도 되는데 차트를 만들기로 했다. 최근에는 매우 귀찮더라도 차트를 매번 작성하는 편인데, 그냥 즉석에서 yaml을 통해 계속 이것저것 배포했더니 현황 유지가 잘 되지 않기 때문이다. 나중에 수습이 안된다. 그래서 억지로라도 차트로 만들어 두고 value를 관리하려고 한다. 더구나 고생해서 어쨌든 차트로 만들어 두면 &lt;code&gt;argocd&lt;/code&gt;같은 곳에서도 편하게 쓸 수 있어서 여러 장점이 생긴다. 그 장점보다 차트를 만드는 수고가 더 드는것 같긴 하지만, 어쨌든 차트를 생성하기 위해서는 &lt;code&gt;helm create&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-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ helm create ecr-refresher
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Creating ecr-refresher
&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;$ tree
&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;├── Chart.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── templates
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── NOTES.txt
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── _helpers.tpl
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── deployment.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── hpa.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── ingress.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── service.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── serviceaccount.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   └── tests
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   └── test-connection.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;└── values.yaml
&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;4&lt;/span&gt; directories, &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; files
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;원래 기본적으로 &lt;code&gt;helm create&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-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ tree
&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;├── Chart.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── README.md
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── templates
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── _helpers.tpl
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── cronjob.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── imagepullsecret.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── role.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── rolebinding.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── secret.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── serviceaccount.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   └── tests
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;└── values.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;values.yaml은 다음과 같은 정보를 담는다.&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;image&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;repository&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;amazon/aws-cli&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;tag&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;2.7.6&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;pullPolicy&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Always&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;pullSecrets&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;cronJob&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;failedJobsHistoryLimit&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;successfulJobsHistoryLimit&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;period&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;* */4 * * *&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;suspend&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;false&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;ecrRegistry&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;registry&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;YOUR_ECR_REGISTRY&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;awsDefaultRegion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;YOUR_ECR_REGION&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;awsAccessKeyID&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;YOUR_ECR_ACCESS_KEY_ID&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;awsSecretAccessKey&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;YOUR_ECR_SECRET_ACCESS_KEY&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;resources&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;limits&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;cpu&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;50m&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;memory&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;16Mi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;requests&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;cpu&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;100m&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;memory&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;32Mi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;nodeSelector&lt;/span&gt;: {}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;tolerations&lt;/span&gt;: []
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;affinity&lt;/span&gt;: {}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;리소스를 간략히 정리해 보자. &lt;code&gt;secret.yaml&lt;/code&gt;은 AWS CLI에 환경 변수로 AWS 관련 정보를 제공한다. 따라서 다음과 같은 data를 가진다.&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;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;Secret&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;type&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Opaque&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;name&lt;/span&gt;: {{ &lt;span style="color:#ae81ff"&gt;include &amp;#34;ecr-refresher.secretName&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;data&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;: {{ &lt;span style="color:#ae81ff"&gt;.Values.ecrRegistry.awsAccessKeyID | b64enc }}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;: {{ &lt;span style="color:#ae81ff"&gt;.Values.ecrRegistry.awsSecretAccessKey | b64enc }}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;AWS_DEFAULT_REGION&lt;/span&gt;: {{ &lt;span style="color:#ae81ff"&gt;.Values.ecrRegistry.awsDefaultRegion | b64enc }}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;imagepullsecret.yaml&lt;/code&gt;은 다른 컨트롤러에서 사용할 image secret이다. 최초 배포되었을 때는 어떤 데이터가 있을지 모르므로 그냥 빈 값을 주도록 했다. 추후 어플리케이션이 배포되고 나서 인증 과정을 거치면 이 데이터를 업데이트할 것이다.&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;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;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;name&lt;/span&gt;: {{ &lt;span style="color:#ae81ff"&gt;include &amp;#34;ecr-refresher.imagePullSecretName&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;data&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;.dockerconfigjson&lt;/span&gt;: {{ &lt;span style="color:#e6db74"&gt;&amp;#34;{}&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;| b64enc }}&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;Secret&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;type&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;kubernetes.io/dockerconfigjson&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;rolebinding과 serviceaccount는 크게 중요한 건 없어서 굳이 쓰지 않아도 되겠고, role만 보면 다음과 같다.&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;name&lt;/span&gt;: {{ &lt;span style="color:#ae81ff"&gt;include &amp;#34;ecr-refresher.roleName&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;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;&amp;#34;&lt;/span&gt;] &lt;span style="color:#75715e"&gt;# &amp;#34;&amp;#34; indicates the core API group&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;secrets&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;resourceNames&lt;/span&gt;: [{{ &lt;span style="color:#ae81ff"&gt;include &amp;#34;ecr-refresher.imagePullSecretName&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;patch&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;이 차트를 통해 어플리케이션이 정상적으로 배포되었다면 항상 imagePullSecret이 존재하므로 여기서 부여하는 권한은 리소스 타입 secrets중 특정한 이름을 가지는 것만 &lt;code&gt;PATCH&lt;/code&gt;할 수 있도록 하였다. 따라서 리소스를 지우거나 타 리소스에 대한 조회 등은 불가능하다.&lt;/p&gt;
&lt;p&gt;마지막으로 _helper.tpl 파일을 확인하자.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;$ cat templates/_helpers.tpl
{{/*
Expand the name of the chart.
*/}}
{{- define &amp;#34;ecr-refresher.name&amp;#34; -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix &amp;#34;-&amp;#34; }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define &amp;#34;ecr-refresher.fullname&amp;#34; -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix &amp;#34;-&amp;#34; }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix &amp;#34;-&amp;#34; }}
{{- else }}
{{- printf &amp;#34;%s-%s&amp;#34; .Release.Name $name | trunc 63 | trimSuffix &amp;#34;-&amp;#34; }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define &amp;#34;ecr-refresher.chart&amp;#34; -}}
{{- printf &amp;#34;%s-%s&amp;#34; .Chart.Name .Chart.Version | replace &amp;#34;+&amp;#34; &amp;#34;_&amp;#34; | trunc 63 | trimSuffix &amp;#34;-&amp;#34; }}
{{- end }}
{{/*
Common labels
*/}}
{{- define &amp;#34;ecr-refresher.labels&amp;#34; -}}
helm.sh/chart: {{ include &amp;#34;ecr-refresher.chart&amp;#34; . }}
{{ include &amp;#34;ecr-refresher.selectorLabels&amp;#34; . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define &amp;#34;ecr-refresher.selectorLabels&amp;#34; -}}
app.kubernetes.io/name: {{ include &amp;#34;ecr-refresher.name&amp;#34; . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define &amp;#34;ecr-refresher.serviceAccountName&amp;#34; -}}
{{- printf &amp;#34;%s-%s&amp;#34; (include &amp;#34;ecr-refresher.fullname&amp;#34; .) &amp;#34;serviceaccount&amp;#34; }}
{{- end }}
{{- define &amp;#34;ecr-refresher.secretName&amp;#34; -}}
{{- printf &amp;#34;%s-%s&amp;#34; (include &amp;#34;ecr-refresher.fullname&amp;#34; .) &amp;#34;env-secret&amp;#34; }}
{{- end }}
{{- define &amp;#34;ecr-refresher.imagePullSecretName&amp;#34; -}}
{{- printf &amp;#34;%s-%s&amp;#34; (include &amp;#34;ecr-refresher.fullname&amp;#34; .) &amp;#34;credentials&amp;#34; }}
{{- end }}
{{- define &amp;#34;ecr-refresher.roleName&amp;#34; -}}
{{- printf &amp;#34;%s-%s&amp;#34; (include &amp;#34;ecr-refresher.fullname&amp;#34; .) &amp;#34;update-role&amp;#34; }}
{{- end }}
{{- define &amp;#34;ecr-refresher.roleBindingName&amp;#34; -}}
{{- printf &amp;#34;%s-%s&amp;#34; (include &amp;#34;ecr-refresher.fullname&amp;#34; .) &amp;#34;rolebinder&amp;#34; }}
{{- end }}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;조금 줄여서 해도 되는데 일부러 전부 다 붙여넣었다. 내용 자체는 자동 생성된 내용을 약간 수정하고 몇 개의 내용을 추가하였다.&lt;/p&gt;
&lt;p&gt;이 차트는 하나의 &lt;code&gt;imagePullSecret&lt;/code&gt;을 업데이트하기 때문에 여러 시크릿을 업데이트하기 위해서는 차트를 다른 이름으로 여러번 배포해야 한다. 따라서 많은 리소스에 대해 이름이 잘 관리되어야 해서 특별히 &lt;code&gt;define&lt;/code&gt;을 통해 여러 리소스의 이름을 관리하도록 하였다. 따라서 배포시 &lt;code&gt;fullnameOverride&lt;/code&gt;를 통해서 완전히 다른 이름을 만들더라도 리소스의 이름이 일관적으로 배포된다.&lt;/p&gt;
&lt;h3 id="kubernetes-rest-api-호출하기"&gt;Kubernetes REST API 호출하기&lt;/h3&gt;
&lt;p&gt;cronjob에서는 AWS CLI를 사용하여 인증 정보를 받아오기 때문에 &lt;code&gt;amazon/aws-cli&lt;/code&gt;를 써야 한다. 그런데 여기서 Secret을 업데이트해야 하므로 &lt;code&gt;kubectl&lt;/code&gt;이 필요한데, 최초에는 이를 그대로 다운로드해서 사용했으나 한 번 호출하고 끝인데다가 사람이 쓰는 것도 아니라서 굳이 이 큰 바이너리를 받아야 할까 싶어, 여기서 바로 curl을 통해 Kubernetes API를 호출하기로 했다. AWS CLI 이미지에는 &lt;code&gt;base64&lt;/code&gt;라던가 &lt;code&gt;curl&lt;/code&gt;은 이미 포함되어 있어서 특별히 뭔가를 준비할 필요 없이 그대로 써도 된다.&lt;/p&gt;
&lt;h4 id="pod-안에서-rest-api-호출하기"&gt;Pod 안에서 REST API 호출하기&lt;/h4&gt;
&lt;p&gt;Pod을 describe하면 다음과 같은 정보를 확인할 수 있다.&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-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl describe pod
&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;Containers:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; task-00002:
&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; State: Terminated
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Reason: Completed
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Exit Code: &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Started: Thu, &lt;span style="color:#ae81ff"&gt;02&lt;/span&gt; Jun &lt;span style="color:#ae81ff"&gt;2022&lt;/span&gt; 07:00:04 +0900
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Finished: Thu, &lt;span style="color:#ae81ff"&gt;02&lt;/span&gt; Jun &lt;span style="color:#ae81ff"&gt;2022&lt;/span&gt; 07:20:11 +0900
&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; Mounts:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-bjj7g &lt;span style="color:#f92672"&gt;(&lt;/span&gt;ro
&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;팟은 &lt;code&gt;serviceaccount&lt;/code&gt;에 대한 정보를 &lt;code&gt;/var/run/secrets/kubernetes.io/serviceaccount&lt;/code&gt;에 항상 마운트하여 실행하도록 되어 있는데, 이를 사용하면 REST API를 호출할 수 있다. 이 경로에 대한 정보는 다음과 같다.&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-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ ls -la /var/run/secrets/kubernetes.io/serviceaccount/
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;total &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;drwxrwxrwt &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt; root root &lt;span style="color:#ae81ff"&gt;140&lt;/span&gt; Jun &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt; 11:39 .
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;drwxr-xr-x &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt; root root &lt;span style="color:#ae81ff"&gt;4096&lt;/span&gt; May &lt;span style="color:#ae81ff"&gt;20&lt;/span&gt; 15:13 ..
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;drwxr-xr-x &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; root root &lt;span style="color:#ae81ff"&gt;100&lt;/span&gt; Jun &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt; 11:39 ..2022_06_05_11_39_01.4056193682
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;lrwxrwxrwx &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; root root &lt;span style="color:#ae81ff"&gt;32&lt;/span&gt; Jun &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt; 11:39 ..data -&amp;gt; ..2022_06_05_11_39_01.4056193682
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;lrwxrwxrwx &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; root root &lt;span style="color:#ae81ff"&gt;13&lt;/span&gt; May &lt;span style="color:#ae81ff"&gt;20&lt;/span&gt; 15:13 ca.crt -&amp;gt; ..data/ca.crt
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;lrwxrwxrwx &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; root root &lt;span style="color:#ae81ff"&gt;16&lt;/span&gt; May &lt;span style="color:#ae81ff"&gt;20&lt;/span&gt; 15:13 namespace -&amp;gt; ..data/namespace
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;lrwxrwxrwx &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; root root &lt;span style="color:#ae81ff"&gt;12&lt;/span&gt; May &lt;span style="color:#ae81ff"&gt;20&lt;/span&gt; 15:13 token -&amp;gt; ..data/token
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ cat /var/run/secrets/kubernetes.io/serviceaccount/token
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;eyJhbG...3skuBdVDkEvTYA/
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ cat /var/run/secrets/kubernetes.io/serviceaccount/namespace
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cublr
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ cat /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-----BEGIN CERTIFICATE-----
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;MIIBdjCCAR2...
&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;-----END CERTIFICATE-----
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;ca.crt: 클러스터 API를 호출하기 위한 ca certificate, curl에서 &lt;code&gt;--insecure&lt;/code&gt;옵션을 줄 거라면 안 써도 된다.&lt;/li&gt;
&lt;li&gt;token: API를 호출하기 위해 필요한 토큰 정보&lt;/li&gt;
&lt;li&gt;namespace: 이 팟이 속한 네임스페이스&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이를 사용하면 단순 curl을 통해 kubernetes API를 호출할 수 있다. endpoint정보는 &lt;a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/"&gt;Kubernetes REST API Reference&lt;/a&gt; 를 확인하면 된다. 어쨌든 pod 리스트를 보고 싶다면 다음과 같은 형태로&amp;hellip; curl을 호출하면 되겠다. Kubernetes의 API Endpoint는 기본 &lt;code&gt;kubernetes.default&lt;/code&gt;이다. 아래에서는 &lt;code&gt;kubernetes.default.svc&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-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;export CA_CERTIFICATE&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;/var/run/secrets/kubernetes.io/serviceaccount/ca.crt&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;export BEARER_TOKEN&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;cat /var/run/secrets/kubernetes.io/serviceaccount/token&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;export NAMESPACE&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;cat /var/run/secrets/kubernetes.io/serviceaccount/namespace&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;curl -X GET --cacert&lt;span style="color:#f92672"&gt;=&lt;/span&gt;$CA_CERTIFICATE --header &lt;span style="color:#e6db74"&gt;&amp;#34;Authorization: Bearer &lt;/span&gt;$BEARER_TOKEN&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; https://kubernetes.default.svc/api/v1/namespaces/$NAMESPACE/pods
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&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-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ curl -X GET --cacert $CA_CERTIFICATE --header &lt;span style="color:#e6db74"&gt;&amp;#34;Authorization: Bearer &lt;/span&gt;$BEARER_TOKEN&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; https://kubernetes.default.svc/api/v1/namespaces/&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;NAMESPACE&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;/pods
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;kind&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Status&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;apiVersion&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;v1&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;metadata&amp;#34;&lt;/span&gt;: &lt;span style="color:#f92672"&gt;{}&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;status&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Failure&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;message&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;pods is forbidden: User \&amp;#34;system:serviceaccount:cublr:default\&amp;#34; cannot list resource \&amp;#34;pods\&amp;#34; in API group \&amp;#34;\&amp;#34; in the namespace \&amp;#34;cublr\&amp;#34;&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;reason&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Forbidden&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;details&amp;#34;&lt;/span&gt;: &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;kind&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;pods&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;}&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;code&amp;#34;&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;403&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;이는 &lt;code&gt;kubectl&lt;/code&gt;에서는 &lt;code&gt;kubectl get pod&lt;/code&gt;을 호출한 것과 같다. kubectl도 다 이 rest api를 호출하게 되어 있다.&lt;/p&gt;
&lt;h4 id="patch-호출하기"&gt;PATCH 호출하기&lt;/h4&gt;
&lt;p&gt;REST API가 이젠 오래 전부터 쓰이는 기술이 되면서 이제 HTTP Method를 굉장히 다양하게 쓰기 시작했는데, 이 PATCH라는 것은&amp;hellip; URL이 가리키는 리소스에 대해 어떤, partially update를 위해 주로 사용한다. kubectl에서는 &lt;code&gt;kubectl patch&lt;/code&gt;명령을 호출한 것과 같다.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#patch-secret-v1-core"&gt;Secret의 PATCH는&lt;/a&gt; &lt;code&gt;PATCH /api/v1/namespaces/{namespace}/secrets/{name}&lt;/code&gt;로 호출하면 된다. 단 변경할 내용의 부분만을 HTTP Body로 전송하면 되는데, &lt;code&gt;dockerconfigjson&lt;/code&gt; 타입 시크릿은 데이터가 &lt;code&gt;data&lt;/code&gt; 밑에 있으므로 data아래 모든 내용을 준다. 그러면 데이터를 어떻게 만들어주냐 하면 이게 좀 번거로운데, helm의 Tip/Trick을 소개하는 페이지에서는 &lt;a href="https://helm.sh/docs/howto/charts_tips_and_tricks/#creating-image-pull-secrets"&gt;다음과 같은 형태로 설명한다.&lt;/a&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;{{- define &amp;#34;imagePullSecret&amp;#34; }}
{{- with .Values.imageCredentials }}
{{- printf &amp;#34;{\&amp;#34;auths\&amp;#34;:{\&amp;#34;%s\&amp;#34;:{\&amp;#34;username\&amp;#34;:\&amp;#34;%s\&amp;#34;,\&amp;#34;password\&amp;#34;:\&amp;#34;%s\&amp;#34;,\&amp;#34;email\&amp;#34;:\&amp;#34;%s\&amp;#34;,\&amp;#34;auth\&amp;#34;:\&amp;#34;%s\&amp;#34;}}}&amp;#34; .registry .username .password .email (printf &amp;#34;%s:%s&amp;#34; .username .password | b64enc) | b64enc }}
{{- end }}
{{- end }}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;이 내용에 써있는대로 조합해서 적당히 &lt;code&gt;b64enc&lt;/code&gt;를 사용해서 만들면 된다. 여기서 &lt;code&gt;.username&lt;/code&gt;은 AWS ECR에서는 &lt;code&gt;AWS&lt;/code&gt;가 된다.&lt;/p&gt;
&lt;p&gt;어쨌든 이렇게 ECR에 로그인 후 가져온 정보를 통해 secret의 내용을 만들었다면 다음과 같이 Kubernetes API를 호출할 수 있다. &lt;code&gt;--header&lt;/code&gt;를 제대로 명시하지 않으면 &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/415"&gt;HTTP 415&lt;/a&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-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ /bin/curl -v &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -X PATCH &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --cacert &lt;span style="color:#e6db74"&gt;${&lt;/span&gt;CACERT&lt;span style="color:#e6db74"&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; --header &lt;span style="color:#e6db74"&gt;&amp;#34;Authorization: Bearer &lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;TOKEN&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&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; --header &lt;span style="color:#e6db74"&gt;&amp;#34;Content-Type: application/merge-patch+json&amp;#34;&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; --data &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$SECRETDATA&lt;span style="color:#e6db74"&gt;&amp;#34;&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:#e6db74"&gt;${&lt;/span&gt;APISERVER&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;/api/v1/namespaces/&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;NAMESPACE&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;/secrets/&lt;span style="color:#f92672"&gt;{{&lt;/span&gt; include &lt;span style="color:#e6db74"&gt;&amp;#34;ecr-refresher.imagePullSecretName&amp;#34;&lt;/span&gt; .&lt;span style="color:#f92672"&gt;}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;제대로 어플리케이션이 배포되었다면 cronjob을 수동으로 한 번 실행하는 것으로 secret이 업데이트되는 것을 확인할 수 있다.&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-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl create job -n cublr --from&lt;span style="color:#f92672"&gt;=&lt;/span&gt;cronjob/cublr-ecr-refresher &lt;span style="color:#ae81ff"&gt;00001&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;job.batch/00001 created
&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;$ kubectl get secret -o yaml -n cublr cublr-ecr-refresher-credentials
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;apiVersion: v1
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;data:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .dockerconfigjson: eyJhdXRocyI6I...FOVBRbz0ifX19Cg&lt;span style="color:#f92672"&gt;==&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kind: Secret
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;metadata:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; annotations:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; meta.helm.sh/release-name: cublr-ecr-refresher
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; meta.helm.sh/release-namespace: cublr
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; creationTimestamp: &lt;span style="color:#e6db74"&gt;&amp;#34;2022-06-05T10:12:41Z&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; labels:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; app.kubernetes.io/managed-by: Helm
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; name: cublr-ecr-refresher-credentials
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; namespace: cublr
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; resourceVersion: &lt;span style="color:#e6db74"&gt;&amp;#34;132513433&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; uid: 7ad5e38b-ec4c-4f44-8da9-3dd2f9ceb531
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;type: kubernetes.io/dockerconfigjson
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;이제 value에 명시된 period마다 secret을 업데이트하게 될 것이다.&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>