<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Kubernetes Archives -</title>
	<atom:link href="https://blog.kwt.co.kr/tag/kubernetes/feed/" rel="self" type="application/rss+xml" />
	<link>https://blog.kwt.co.kr/tag/kubernetes/</link>
	<description>여러분의 돈과 시간을 낭비하지마세요.</description>
	<lastBuildDate>Tue, 24 Feb 2026 00:32:44 +0000</lastBuildDate>
	<language>ko-KR</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.6.2</generator>

<image>
	<url>https://blog.kwt.co.kr/wp-content/uploads/2022/07/cropped-logo_bg-32x32.jpg</url>
	<title>Kubernetes Archives -</title>
	<link>https://blog.kwt.co.kr/tag/kubernetes/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Kubernetes 온프레미스 클러스터 업그레이드하기</title>
		<link>https://blog.kwt.co.kr/kubernetes-%ec%98%a8%ed%94%84%eb%a0%88%eb%af%b8%ec%8a%a4-%ed%81%b4%eb%9f%ac%ec%8a%a4%ed%84%b0-%ec%97%85%ea%b7%b8%eb%a0%88%ec%9d%b4%eb%93%9c%ed%95%98%ea%b8%b0/</link>
					<comments>https://blog.kwt.co.kr/kubernetes-%ec%98%a8%ed%94%84%eb%a0%88%eb%af%b8%ec%8a%a4-%ed%81%b4%eb%9f%ac%ec%8a%a4%ed%84%b0-%ec%97%85%ea%b7%b8%eb%a0%88%ec%9d%b4%eb%93%9c%ed%95%98%ea%b8%b0/#respond</comments>
		
		<dc:creator><![CDATA[시간 조절자]]></dc:creator>
		<pubDate>Sun, 22 Feb 2026 14:58:08 +0000</pubDate>
				<category><![CDATA[기술]]></category>
		<category><![CDATA[쿠버네티스]]></category>
		<category><![CDATA[CKA]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[etcd]]></category>
		<category><![CDATA[kubeadm]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[온프레미스]]></category>
		<category><![CDATA[클러스터 업그레이드]]></category>
		<guid isPermaLink="false">https://blog.kwt.co.kr/?p=1619</guid>

					<description><![CDATA[<p>6노드 온프레미스 Kubernetes 클러스터를 v1.30.4에서 v1.31.14로 업그레이드한 과정을 정리한다. etcd 백업부터 control-plane, worker 노드 순차 업그레이드까지 실전에서 주의할 점을 공유한다.</p>
<p>The post <a href="https://blog.kwt.co.kr/kubernetes-%ec%98%a8%ed%94%84%eb%a0%88%eb%af%b8%ec%8a%a4-%ed%81%b4%eb%9f%ac%ec%8a%a4%ed%84%b0-%ec%97%85%ea%b7%b8%eb%a0%88%ec%9d%b4%eb%93%9c%ed%95%98%ea%b8%b0/">Kubernetes 온프레미스 클러스터 업그레이드하기</a> appeared first on <a href="https://blog.kwt.co.kr"></a>.</p>
]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">들어가며</h2>



<p><a href="https://blog.kwt.co.kr/?p=744">이전 포스팅</a>에서 집에 굴러다니는 미니PC들로 쿠버네티스 클러스터를 구축한 이야기를 했었다.</p>



<p><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-black-color"><a href="https://kwt.co.kr/kubernetes">쿠버네티스 클러스터 둘러보기</a></mark></p>



<p>그때 설치한 버전이 v1.30.4였는데, 그 뒤로 Jenkins, Kafka, MySQL InnoDB Cluster, Redis 같은 것들을 하나씩 올리면서 &#8220;잘 돌아가는데 굳이 건드려야 하나&#8221; 싶어서 업그레이드를 계속 미뤄왔다. 근데 v1.30 지원 종료(EOL)도 되었고(on-prem은 2025년에 만료), CKA 시험 준비를 하면서 kubeadm 업그레이드를 공부하다 보니 이참에 직접 해보자 싶었다.</p>



<p>실제 프로덕션 워크로드가 돌아가는 환경에서 하는 거라 우려했는데, 막상 절차대로 하니까 생각보다 어렵지 않았다. 그 과정을 기록해둔다.</p>



<h2 class="wp-block-heading">3줄 요약</h2>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<ul class="wp-block-list">
<li>Kubernetes는 <strong>한 번에 1 마이너 버전씩만</strong> 업그레이드할 수 있다 (v1.30 → v1.31 OK, v1.30 → v1.32 불가. <s>도대체 왜 이렇게 만든건가?</s>)</li>



<li>반드시 <strong>control-plane 먼저, worker 나중에</strong> 순서를 지켜야 한다</li>



<li>업그레이드 전 <strong>etcd 백업은 필수</strong> &#8211; 실패 시 복구할 수 있는 유일한 보험</li>
</ul>
</blockquote>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Kubernetes 클러스터 구성</h2>



<p>업그레이드 대상 클러스터 구성은 이렇게 생겼다:</p>



<p>Kubernetes Cluster v1.30.4</p>



<p>Control Plane (3대)</p>



<ul class="wp-block-list">
<li>luckys-worker0
<ul class="wp-block-list">
<li>ingress-nginx-controller</li>



<li>redis-node-0</li>



<li>mysql-operator</li>
</ul>
</li>



<li>luckys-worker1
<ul class="wp-block-list">
<li>ingress-nginx-controller</li>



<li>dev-mysql-cluster-0</li>
</ul>
</li>



<li>luckys-worker2
<ul class="wp-block-list">
<li>kafka-controller-2</li>



<li>redis-node-2</li>
</ul>
</li>
</ul>



<p>Worker (3대)</p>



<ul class="wp-block-list">
<li><s>luckys-worker3 &#8211; 사망</s></li>



<li>luckys-worker4
<ul class="wp-block-list">
<li>kafka-controller-1</li>



<li>prod-mysql-cluster-1</li>



<li>loki (로그 수집)</li>
</ul>
</li>



<li>luckys-worker5
<ul class="wp-block-list">
<li>kafka-controller-0</li>



<li>prod-mysql-cluster-2</li>



<li>prometheus, alertmanager</li>



<li>redis-dev-master (standalone)</li>
</ul>
</li>



<li>luckys-worker6
<ul class="wp-block-list">
<li>prod-mysql-cluster-0</li>



<li>ingress-nginx-controller</li>



<li>jenkins</li>



<li>nexus, openclaw, grafana</li>
</ul>
</li>
</ul>



<p>주요 워크로드: Jenkins, Kafka, MySQL InnoDB Cluster, Redis Sentinel, Longhorn, MetalLB, Ingress-Nginx, 이외 다수 Application</p>



<p>OS는 Ubuntu 24.04 LTS, 컨테이너 런타임은 containerd.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">사전 준비</h2>



<h3 class="wp-block-heading">1. 현재 버전 확인</h3>



<pre class="wp-block-code"><code>kubeadm version
# kubeadm version: v1.30.4

kubelet --version
# Kubernetes v1.30.4

kubectl version</code></pre>



<h3 class="wp-block-heading">2. 업그레이드 가능한 버전 확인</h3>



<p>v1.31 저장소를 추가하고 사용 가능한 버전을 확인한다:</p>



<pre class="wp-block-code"><code># v1.31 저장소 추가
echo 'deb &#91;signed-by=/etc/apt/keyrings/kubernetes-apt-keyring-v1.31.gpg] https://pkgs.k8s.io/core:/stable:/v1.31/deb/ /' \
  | sudo tee /etc/apt/sources.list.d/kubernetes-v1.31.list

# GPG 키 등록
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.31/deb/Release.key \
  | sudo gpg --dearmor --yes -o /etc/apt/keyrings/kubernetes-apt-keyring-v1.31.gpg

sudo apt update
sudo apt-cache madison kubeadm | head -5</code></pre>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><strong>참고:</strong> 기존 v1.30 저장소의 GPG 키가 만료되어 <code>apt update</code> 시 에러가 발생할 수 있다. v1.31 저장소만 정상이면 업그레이드 진행에 문제없다.</p>
</blockquote>



<h3 class="wp-block-heading">3. etcd 백업 (필수!)</h3>



<p>업그레이드 전 반드시 etcd를 백업한다. 문제가 생기면 이 백업으로 클러스터를 복구할 수 있다.</p>



<pre class="wp-block-code"><code># 인증서 경로 확인
kubectl describe pod etcd-luckys-worker0 -n kube-system

# etcd 백업 실행
export ETCDCTL_API=3
etcdctl snapshot save /opt/etcd-backup-before-upgrade.db \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key

# 백업 확인
etcdctl snapshot status /opt/etcd-backup-before-upgrade.db --write-out=table</code></pre>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>etcdctl이 설치되어 있지 않다면 <code>kubectl exec</code>으로 etcd Pod 안에서 실행하거나, etcd 바이너리를 직접 설치하면 된다.</p>
</blockquote>



<h3 class="wp-block-heading">4. 노드별 워크로드 분포 확인</h3>



<p>drain 하면 영향받는 워크로드를 미리 파악해야 한다:</p>



<pre class="wp-block-code"><code># 각 노드에서 돌아가는 Pod 확인
kubectl get pods -A -o wide --field-selector spec.nodeName=luckys-worker0 | grep -v kube-system

# Taint 확인 (control-plane에 Taint가 없으면 일반 워크로드도 올라가 있을 수 있음)
kubectl describe node luckys-worker0 | grep -i taint</code></pre>



<p>내 클러스터는 control-plane에 Taint가 설정되어 있지 않아서(마스터도 예외 없다) 일반 워크로드도 control-plane 노드에서 실행되고 있었다. MySQL InnoDB Cluster, Redis, Ingress 등의 분포를 확인하고 drain해도 프로덕션에 영향이 없는지 검증한 후 진행했다.</p>



<h3 class="wp-block-heading">업그레이드 시 안전도</h3>



<p>비교적 안전</p>



<ul class="wp-block-list">
<li>luckys-worker0 (prod 1개뿐, MySQL/Kafka 없음)</li>



<li>luckys-worker2 (워크로드 적음)</li>
</ul>



<p>우려됨</p>



<ul class="wp-block-list">
<li>luckys-worker1 (prod 앱 + ingress)</li>



<li>luckys-worker5 (모니터링 + MySQL + Kafka)</li>
</ul>



<p>매우 우려됨</p>



<ul class="wp-block-list">
<li>luckys-worker4 (워크로드 최다 + MySQL + Kafka)</li>



<li>luckys-worker6 (prod 앱 많음 + MySQL + Jenkins + Ingress)</li>
</ul>



<p>사실 다른 것도 그렇지만, Longhorn 으로 데이터가 서로 다른 노드에 동기화 되어야 하는데, upgrade로 인한 중단 시 쓰기 지연이 발생할 경우 클러스터 전체의 성능이 대폭 하락하는 문제가 있어서 이 지점이 가장 골머리 아프다.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">업그레이드 순서</h2>



<p><strong>반드시 control-plane → worker 순서로 해야한다.</strong> kubelet은 apiserver보다 높은 버전일 수 없기 때문이다.</p>



<pre class="wp-block-code"><code>1. Control Plane #1 (luckys-worker0) ← 첫 번째는 kubeadm upgrade apply
2. Control Plane #2 (luckys-worker1) ← 이후는 kubeadm upgrade node
3. Control Plane #3 (luckys-worker2)
4. Worker #1 (luckys-worker4)        ← 전부 kubeadm upgrade node
5. Worker #2 (luckys-worker5)
6. Worker #3 (luckys-worker6)</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Control Plane 첫 번째 노드 업그레이드</h2>



<p>첫 번째 control-plane 노드만 <code>kubeadm upgrade apply</code>를 사용한다. 나머지는 전부 <code>kubeadm upgrade node</code>를 쓴다.</p>



<h3 class="wp-block-heading">Step 1: kubeadm 업그레이드</h3>



<pre class="wp-block-code"><code># kubeadm 패키지 잠금 해제 → 설치 → 다시 잠금
apt-mark unhold kubeadm &amp;&amp; \
apt-get update &amp;&amp; apt-get install -y kubeadm=1.31.14-1.1 &amp;&amp; \
apt-mark hold kubeadm</code></pre>



<h3 class="wp-block-heading">Step 2: 업그레이드 계획 확인 및 실행</h3>



<pre class="wp-block-code"><code># 버전 확인
kubeadm version

# 업그레이드 계획 확인
sudo kubeadm upgrade plan

# 업그레이드 실행
sudo kubeadm upgrade apply v1.31.14</code></pre>



<p><code>kubeadm upgrade plan</code>은 현재 상태를 분석해서 업그레이드 가능 여부를 보여준다. 문제가 없으면 <code>apply</code>로 실제 업그레이드를 진행한다.</p>



<h3 class="wp-block-heading">Step 3: 노드에서 Pod 퇴거 (drain)</h3>



<p>kubeadm upgrade 후, kubelet 업그레이드 전에 drain한다.</p>



<pre class="wp-block-code"><code>kubectl drain luckys-worker0 --ignore-daemonsets</code></pre>



<ul class="wp-block-list">
<li><code>--ignore-daemonsets</code>: DaemonSet Pod(모니터링, 네트워크 등)은 무시</li>



<li>emptyDir 사용하는 Pod 때문에 실패하면 <code>--delete-emptydir-data</code> 추가</li>
</ul>



<h3 class="wp-block-heading">Step 4: kubelet &amp; kubectl 업그레이드</h3>



<pre class="wp-block-code"><code># 패키지 잠금 해제 → 설치 → 다시 잠금
apt-mark unhold kubelet kubectl &amp;&amp; \
apt-get update &amp;&amp; apt-get install -y kubelet=1.31.14-1.1 kubectl=1.31.14-1.1 &amp;&amp; \
apt-mark hold kubelet kubectl

# kubelet 재시작
sudo systemctl daemon-reload
sudo systemctl restart kubelet</code></pre>



<h3 class="wp-block-heading">Step 5: 노드 복귀 (uncordon)</h3>



<pre class="wp-block-code"><code>kubectl uncordon luckys-worker0</code></pre>



<h3 class="wp-block-heading">Step 6: 업그레이드 확인</h3>



<pre class="wp-block-code"><code>kubectl get nodes
# luckys-worker0의 VERSION이 v1.31.14로 변경되었는지 확인</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">나머지 Control Plane 노드 업그레이드</h2>



<p>두 번째, 세 번째 control-plane 노드는 <strong><code>kubeadm upgrade node</code></strong>를 사용한다. <code>apply</code>가 아닌 점에 주의. <code>kubeadm upgrade plan</code>도 불필요하다.</p>



<p>각 노드에 SSH 접속 후:</p>



<pre class="wp-block-code"><code># 1. kubeadm 업그레이드
apt-mark unhold kubeadm &amp;&amp; \
apt-get update &amp;&amp; apt-get install -y kubeadm=1.31.14-1.1 &amp;&amp; \
apt-mark hold kubeadm

# 2. 노드 업그레이드
sudo kubeadm upgrade node          # ← apply가 아닌 node!

# 3. drain (다른 control-plane 노드에서 실행)
kubectl drain luckys-worker1 --ignore-daemonsets

# 4. kubelet &amp; kubectl 업그레이드
apt-mark unhold kubelet kubectl &amp;&amp; \
apt-get update &amp;&amp; apt-get install -y kubelet=1.31.14-1.1 kubectl=1.31.14-1.1 &amp;&amp; \
apt-mark hold kubelet kubectl

sudo systemctl daemon-reload
sudo systemctl restart kubelet

# 5. uncordon (다른 노드에서 실행)
kubectl uncordon luckys-worker1</code></pre>



<p>luckys-worker2도 동일하게 진행한다.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Worker 노드 업그레이드</h2>



<p>Worker 노드도 거의 동일하다. <code>kubeadm upgrade node</code>를 사용한다.</p>



<pre class="wp-block-code"><code># 1. kubeadm 업그레이드
apt-mark unhold kubeadm &amp;&amp; \
apt-get update &amp;&amp; apt-get install -y kubeadm=1.31.14-1.1 &amp;&amp; \
apt-mark hold kubeadm

# 2. 노드 업그레이드
sudo kubeadm upgrade node

# 3. drain (control-plane에서 실행)
kubectl drain luckys-worker4 --ignore-daemonsets

# 4. kubelet &amp; kubectl
apt-mark unhold kubelet kubectl &amp;&amp; \
apt-get update &amp;&amp; apt-get install -y kubelet=1.31.14-1.1 kubectl=1.31.14-1.1 &amp;&amp; \
apt-mark hold kubelet kubectl

sudo systemctl daemon-reload
sudo systemctl restart kubelet

# 5. uncordon (control-plane에서 실행)
kubectl uncordon luckys-worker4</code></pre>



<p>luckys-worker5, luckys-worker6도 동일하게 진행한다.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">전체 업그레이드 완료 확인</h2>



<pre class="wp-block-code"><code>kubectl get nodes -o wide</code></pre>



<pre class="wp-block-code"><code>NAME             STATUS   ROLES           VERSION    OS-IMAGE
luckys-worker0   Ready    control-plane   v1.31.14   Ubuntu 24.04 LTS
luckys-worker1   Ready    control-plane   v1.31.14   Ubuntu 24.04 LTS
luckys-worker2   Ready    control-plane   v1.31.14   Ubuntu 24.04 LTS
luckys-worker4   Ready    &lt;none&gt;          v1.31.14   Ubuntu 24.04 LTS
luckys-worker5   Ready    &lt;none&gt;          v1.31.14   Ubuntu 24.04 LTS
luckys-worker6   Ready    &lt;none&gt;          v1.31.14   Ubuntu 24.04 LTS</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">주의사항 &amp; 삽질 기록</h2>



<h3 class="wp-block-heading">GPG 키 만료 문제</h3>



<p>v1.30 저장소의 GPG 키가 만료되어 <code>apt update</code> 시 에러가 났다:</p>



<pre class="wp-block-code"><code>EXPKEYSIG 234654DA9A296436 isv:kubernetes OBS Project</code></pre>



<p>v1.31 저장소를 새로 추가하고 키를 등록하면 해결된다. 기존 v1.30 저장소 에러는 무시해도 된다.</p>



<p><strong>1. v1.31 GPG 키 다운로드 및 등록</strong></p>



<pre class="wp-block-code"><code>curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.31/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring-v1.31.gpg</code></pre>



<p><strong>2. v1.31 저장소 추가</strong></p>



<pre class="wp-block-code"><code>echo 'deb &#91;signed-by=/etc/apt/keyrings/kubernetes-apt-keyring-v1.31.gpg] https://pkgs.k8s.io/core:/stable:/v1.31/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes-v1.31.list</code></pre>



<h3 class="wp-block-heading">control-plane에 Taint가 없는 경우</h3>



<p>일반적으로 control-plane 노드에는 <code>NoSchedule</code> Taint가 설정되어 있어서 일반 워크로드가 배치되지 않는다. 근데 내 클러스터처럼 Taint가 없으면 MySQL, Redis, Ingress 등이 control-plane에서도 실행된다.(Taint를 설정하기엔 비용 이슈가..)</p>



<p>drain 전에 반드시 해당 노드의 워크로드를 확인하고, 프로덕션 영향을 검토해야 한다:</p>



<pre class="wp-block-code"><code>kubectl get pods -A -o wide --field-selector spec.nodeName=&lt;노드명&gt; | grep -v kube-system</code></pre>



<h3 class="wp-block-heading">drain vs cordon</h3>



<ul class="wp-block-list">
<li><code>kubectl drain</code>: 기존 Pod를 다른 노드로 퇴거시키고 스케줄 차단</li>



<li><code>kubectl cordon</code>: 새 Pod 스케줄만 차단, 기존 Pod는 그대로</li>
</ul>



<p>프로덕션 영향이 걱정되면 <code>cordon</code>만 하고 업그레이드를 진행하는 방법도 있다. kubelet 재시작 시 잠깐 중단되지만 Pod가 다른 노드로 이동하지는 않는다.</p>



<h3 class="wp-block-heading">MySQL InnoDB Cluster 고려</h3>



<p>MySQL InnoDB Cluster는 3개 인스턴스가 서로 다른 노드에 분산되어 있어서, 한 노드를 drain해도 나머지 2개가 쿼럼을 유지한다. drain 전에 어떤 노드에 어떤 인스턴스가 있는지 확인하자:</p>



<pre class="wp-block-code"><code>kubectl get pods -A -o wide | grep mysql
kubectl get innodbcluster -A</code></pre>



<h3 class="wp-block-heading">한 대씩, 확인하면서</h3>



<p>절대 여러 노드를 동시에 drain하지 말자. 특히 control-plane은 etcd 쿼럼(과반수) 유지가 필수다. 3대 중 2대가 동시에 내려가면 클러스터가 멈춘다.</p>



<pre class="wp-block-code"><code>한 대 업그레이드 완료 → kubectl get nodes로 Ready 확인 → 다음 노드</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">업그레이드 절차 요약</h2>



<pre class="wp-block-code"><code>&#91;사전 준비]
  etcd 백업 → 저장소 추가 → 워크로드 분포 확인

&#91;Control Plane 첫 번째 노드]
  kubeadm 설치 → kubeadm upgrade apply → drain → kubelet kubectl 설치 → restart → uncordon

&#91;Control Plane 나머지 + Worker 전체]
  kubeadm 설치 → kubeadm upgrade node → drain → kubelet kubectl 설치 → restart → uncordon

&#91;완료]
  kubectl get nodes로 전체 버전 확인</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">마치며</h2>



<p>막상 해보니 절차만 지키면 크게 어렵지 않아서 이걸 왜이리 미뤄왔나 싶다.</p>



<p>핵심은 세 가지다:</p>



<ol class="wp-block-list">
<li><strong>etcd 백업</strong>: 만약을 위한 보험</li>



<li><strong>순서 준수</strong>: control-plane 먼저, worker 나중에</li>



<li><strong>한 대씩</strong>: 확인하고 넘어가기</li>
</ol>



<p>CKA 시험에서도 kubeadm 업그레이드는 거의 매번 출제되는 문제라고 한다. 실제 클러스터에서 한 번 해보면 시험에서도 별 문제 없이 풀 수 있을 것 같다.</p>



<h3 class="wp-block-heading">참고 링크</h3>



<ul class="wp-block-list">
<li><a href="https://kubernetes.io/docs/tasks/administer-cluster/kubeadm/kubeadm-upgrade/">Kubernetes 공식 문서 &#8211; Upgrading kubeadm clusters</a></li>



<li><a href="https://kubernetes.io/releases/version-skew-policy/">Kubernetes Version Skew Policy</a></li>



<li><a href="https://training.linuxfoundation.org/certification/certified-kubernetes-administrator-cka/">CKA 시험 공식 페이지</a></li>



<li><a href="https://blog.kwt.co.kr/?p=744">홈 서버 쿠버네티스 클러스터 구축기</a></li>
</ul>
		<div class="wpulike wpulike-robeen " ><div class="wp_ulike_general_class wp_ulike_is_restricted"><button type="button"
					aria-label="Like Button"
					data-ulike-id="1619"
					data-ulike-nonce="5853ebbf77"
					data-ulike-type="post"
					data-ulike-template="wpulike-robeen"
					data-ulike-display-likers=""
					data-ulike-likers-style="popover"
					class="wp_ulike_btn wp_ulike_put_image wp_post_btn_1619"></button><span class="count-box wp_ulike_counter_up" data-ulike-counter-value="0"></span>			</div></div>
	<p>The post <a href="https://blog.kwt.co.kr/kubernetes-%ec%98%a8%ed%94%84%eb%a0%88%eb%af%b8%ec%8a%a4-%ed%81%b4%eb%9f%ac%ec%8a%a4%ed%84%b0-%ec%97%85%ea%b7%b8%eb%a0%88%ec%9d%b4%eb%93%9c%ed%95%98%ea%b8%b0/">Kubernetes 온프레미스 클러스터 업그레이드하기</a> appeared first on <a href="https://blog.kwt.co.kr"></a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.kwt.co.kr/kubernetes-%ec%98%a8%ed%94%84%eb%a0%88%eb%af%b8%ec%8a%a4-%ed%81%b4%eb%9f%ac%ec%8a%a4%ed%84%b0-%ec%97%85%ea%b7%b8%eb%a0%88%ec%9d%b4%eb%93%9c%ed%95%98%ea%b8%b0/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>OpenClaw &#8211; Kubernetes 클러스터에 구축기 with Claude Code</title>
		<link>https://blog.kwt.co.kr/openclaw-kubernetes-%ed%81%b4%eb%9f%ac%ec%8a%a4%ed%84%b0%ec%97%90-%ea%b5%ac%ec%b6%95%ed%95%98%ea%b8%b0/</link>
					<comments>https://blog.kwt.co.kr/openclaw-kubernetes-%ed%81%b4%eb%9f%ac%ec%8a%a4%ed%84%b0%ec%97%90-%ea%b5%ac%ec%b6%95%ed%95%98%ea%b8%b0/#respond</comments>
		
		<dc:creator><![CDATA[시간 조절자]]></dc:creator>
		<pubDate>Sun, 08 Feb 2026 03:56:38 +0000</pubDate>
				<category><![CDATA[기술]]></category>
		<category><![CDATA[AI Agent]]></category>
		<category><![CDATA[CI/CD]]></category>
		<category><![CDATA[Claude Code]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[Helm]]></category>
		<category><![CDATA[Jenkins]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[OpenClaw]]></category>
		<guid isPermaLink="false">https://blog.kwt.co.kr/?p=1461</guid>

					<description><![CDATA[<p>오픈소스 AI 자동화 프레임워크 OpenClaw을 Kubernetes 클러스터에 설치하고, kubectl/Jenkins/GitHub과 연동하여 자율적으로 코드 작성부터 배포까지 수행하는 DevOps 에이전트를 구축한 과정을 공유합니다.</p>
<p>The post <a href="https://blog.kwt.co.kr/openclaw-kubernetes-%ed%81%b4%eb%9f%ac%ec%8a%a4%ed%84%b0%ec%97%90-%ea%b5%ac%ec%b6%95%ed%95%98%ea%b8%b0/">OpenClaw &#8211; Kubernetes 클러스터에 구축기 with Claude Code</a> appeared first on <a href="https://blog.kwt.co.kr"></a>.</p>
]]></description>
										<content:encoded><![CDATA[


<h2 class="wp-block-heading">들어가며</h2>



<p>&#8220;AI에게 서버 관리를 맡길 수 있을까?&#8221;</p>



<p>최근 AI 에이전트 기술이 빠르게 발전하면서, 단순한 챗봇을 넘어 <strong>실제 인프라를 관리하는 AI</strong>에 대한 관심이 높아지고 있습니다. 이번 글에서는 오픈소스 AI 자동화 프레임워크인 <strong>OpenClaw</strong>을 Kubernetes 클러스터에 설치하고, <strong>코드 작성 → GitHub 푸시 → Jenkins 빌드 → K8s 배포</strong>까지 자율적으로 수행하는 DevOps 에이전트를 구축한 과정을 공유합니다.</p>



<p>전체 구현 과정은 <strong>Claude Code</strong>(Anthropic의 CLI 기반 AI 코딩 도구)와 함께 진행했으며, 설정 파일 작성부터 트러블슈팅까지 실시간으로 협업하며 완성했습니다.</p>



<figure class="wp-block-image size-full is-resized"><img fetchpriority="high" decoding="async" width="1516" height="1398" src="https://blog.kwt.co.kr/wp-content/uploads/2026/02/스크린샷-2026-02-08-오후-12.27.19.png" alt="openclaw 가재상 열일" class="wp-image-1464" style="width:582px;height:auto"/></figure>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">핵심요약</h2>



<ul class="wp-block-list">
<li>쿠버네티스 클러스터 내에 pod 로 실행중</li>



<li>슬랙과 연동해서 대화형으로 구성</li>



<li>GLM-4.7 (영균매니저님이 소개해준 Z.AI)을 main model 로 사용</li>



<li>pod 에서 상위 레벨인 node 상태와 클러스터 제어를 위해 권한 부여(service account의 RBAC 설정)</li>



<li>GLM 모델 특성(원래 느림)도 있겠지만 상호작용이 개느림, 답답</li>
</ul>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">OpenClaw ?</h2>



<p><strong>OpenClaw</strong>은 2026년 초 등장한 오픈소스 AI 자동화 프레임워크입니다. 주요 특징은 다음과 같습니다:</p>



<ul class="wp-block-list">
<li><strong>다양한 LLM 지원</strong>: Anthropic Claude, OpenAI GPT, Google Gemini, Z.AI GLM 등</li>



<li><strong>멀티 채널</strong>: 웹 UI, Slack, Discord, WhatsApp 등으로 대화 가능</li>



<li><strong>도구 실행</strong>: 셸 명령어, 파일 조작, 웹 브라우저 자동화 기능 내장</li>



<li><strong>Helm Chart 제공</strong>: Kubernetes 배포를 위한 공식 Helm 차트 지원</li>
</ul>



<p>핵심은 AI가 단순히 텍스트를 생성하는 것을 넘어, <strong>실제 명령어를 실행하고 시스템을 제어</strong>할 수 있다는 점입니다.</p>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">최종 아키텍처</h2>



<p>구축 완료 후의 전체 아키텍처는 다음과 같습니다:</p>



<pre class="wp-block-code"><code>┌─────────────────────────────────────────────────────────┐
│  Kubernetes Cluster (v1.30.4, 6 nodes)                  │
│                                                         │
│  ┌─────────── openclaw namespace ──────────┐            │
│  │  ┌──────────────────────────────────┐   │            │
│  │  │  OpenClaw Pod (2 containers)     │   │            │
│  │  │  ├─ main: OpenClaw Agent         │   │            │
│  │  │  │   ├─ kubectl (RBAC)    ──────────────→ K8s API │
│  │  │  │   ├─ helm              ──────────────→ K8s API │
│  │  │  │   ├─ git               ──────────────→ GitHub  │
│  │  │  │   └─ curl              ──────────────→ Jenkins │
│  │  │  └─ chromium: Browser Sidecar    │   │            │
│  │  └──────────────────────────────────┘   │            │
│  │  ServiceAccount: openclaw-sa            │            │
│  │  ClusterRole: Full K8s Access           │            │
│  └─────────────────────────────────────────┘            │
│                                                         │
│  ┌─── corpbreak-com-ingress ns ───┐                     │
│  │  ExternalName Svc + Ingress    │                     │
│  │  → openclaw.corpbreak.com      │                     │
│  └────────────────────────────────┘                     │
└─────────────────────────────────────────────────────────┘
         &#x2195;                    &#x2195;                &#x2195;
    &#091;Slack Bot]         &#091;Web UI HTTPS]    &#091;Jenkins API]
</code></pre>



<h3 class="wp-block-heading">주요 구성 요소</h3>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>구성 요소</th><th>역할</th></tr></thead><tbody><tr><td>OpenClaw Main Container</td><td>AI 에이전트 엔진, LLM 호출, 도구 실행</td></tr><tr><td>Chromium Sidecar</td><td>브라우저 자동화 (웹 스크래핑, 스크린샷)</td></tr><tr><td>ServiceAccount + RBAC</td><td>Pod에서 K8s API 접근 권한 부여</td></tr><tr><td>Init Containers</td><td>kubectl, helm 바이너리 설치 + 시스템 프롬프트 주입</td></tr><tr><td>Ingress</td><td>HTTPS 외부 접근 (WebSocket 지원)</td></tr></tbody></table></figure>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">구현 과정</h2>



<h3 class="wp-block-heading">Step 1: Helm으로 기본 설치</h3>



<p>먼저 OpenClaw의 공식 Helm 차트를 이용하여 기본 설치를 진행했습니다.</p>



<pre class="wp-block-code"><code># Helm 레포지토리 추가
helm repo add openclaw https://serhanekicii.github.io/openclaw-helm
helm repo update

# 네임스페이스 생성 및 설치
kubectl create namespace openclaw
helm install openclaw openclaw/openclaw -n openclaw -f values.yaml
</code></pre>



<p>LLM은 Z.AI의 <strong>GLM 4.7</strong> 모델을 선택했습니다. OpenClaw은 <code>zai</code>를 빌트인 프로바이더로 지원하기 때문에, API 키만 환경변수로 설정하면 됩니다.</p>



<h3 class="wp-block-heading">Step 2: 첫 번째 장애 &#8211; Gateway Token</h3>



<p>설치 직후 Pod가 <strong>CrashLoopBackOff</strong> 상태에 빠졌습니다.</p>



<pre class="wp-block-code"><code>Config invalid
Problem: Gateway auth is set to token, but no token is configured
</code></pre>



<p>OpenClaw의 Gateway는 인증 토큰이 필수입니다. <code>openssl rand -hex 32</code>로 토큰을 생성하고 Secret에 추가한 후, 설정 파일에서 환경변수로 참조하도록 수정했습니다.</p>



<pre class="wp-block-code"><code>"gateway": {
  "auth": {
    "token": "${GATEWAY_TOKEN}"
  }
}
</code></pre>



<h3 class="wp-block-heading">Step 3: HTTPS Ingress 구성</h3>



<p>기존 인프라의 패턴에 맞춰 <strong>ExternalName Service + Ingress</strong> 조합으로 외부 접근을 구성했습니다.</p>



<pre class="wp-block-code"><code># ExternalName Service (cross-namespace routing)
apiVersion: v1
kind: Service
metadata:
  name: openclaw-external
  namespace: corpbreak-com-ingress
spec:
  type: ExternalName
  externalName: openclaw.openclaw.svc.cluster.local
</code></pre>



<p>OpenClaw의 WebUI는 <strong>WebSocket</strong>을 사용하므로, Ingress에 다음 annotation이 필수입니다:</p>



<pre class="wp-block-code"><code>nginx.ingress.kubernetes.io/proxy-http-version: "1.1"
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
</code></pre>



<h3 class="wp-block-heading">Step 4: WebSocket 연결 오류 해결</h3>



<p>접속 시도 시 여러 WebSocket 에러가 연이어 발생했습니다:</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>에러</th><th>원인</th><th>해결</th></tr></thead><tbody><tr><td><code>origin not allowed</code></td><td>CORS 설정 누락</td><td><code>gateway.controlUi.allowedOrigins</code> 추가</td></tr><tr><td><code>gateway token missing</code></td><td>토큰 전달 방식</td><td>URL 쿼리 파라미터로 토큰 전달</td></tr><tr><td><code>pairing required</code></td><td>디바이스 승인 필요</td><td><code>kubectl exec</code>로 디바이스 승인</td></tr></tbody></table></figure>



<p>각 오류를 하나씩 해결한 끝에 웹 UI 접속에 성공했습니다.</p>



<figure class="wp-block-image size-full"><img decoding="async" width="1718" height="800" src="https://blog.kwt.co.kr/wp-content/uploads/2026/02/스크린샷-2026-02-08-오후-12.36.34.png" alt="openclaw ui 첫대화" class="wp-image-1466"/></figure>



<h3 class="wp-block-heading">Step 5: Slack 연동</h3>



<p>OpenClaw은 Slack Socket Mode를 지원합니다. Slack App을 생성하고 다음 두 토큰을 Secret에 추가하면 됩니다:</p>



<ul class="wp-block-list">
<li><strong>App-Level Token</strong> (<code>xapp-</code>): Socket Mode 연결용</li>



<li><strong>Bot Token</strong> (<code>xoxb-</code>): 메시지 송수신용</li>
</ul>



<pre class="wp-block-code"><code>"channels": {
  "slack": {
    "enabled": true,
    "appToken": "${SLACK_APP_TOKEN}",
    "botToken": "${SLACK_BOT_TOKEN}"
  }
}
</code></pre>



<p>설정 후 OpenClaw 로그에서 <code>socket mode connected</code> 메시지를 확인할 수 있습니다.</p>



<figure class="wp-block-image size-full is-resized"><img decoding="async" width="442" height="436" src="https://blog.kwt.co.kr/wp-content/uploads/2026/02/image-2.png" alt="openclaw, 가재상 첫 대답" class="wp-image-1467" style="width:305px;height:auto"/></figure>



<h3 class="wp-block-heading">Step 6: 브라우저 자동화 설정</h3>



<p>OpenClaw의 브라우저 기능을 활성화하려면 두 가지가 필요합니다:</p>



<ol class="wp-block-list">
<li><strong>Chromium Sidecar Container</strong>: CDP(Chrome DevTools Protocol) 서버 제공</li>



<li><strong>Playwright 브라우저 바이너리</strong>: Main 컨테이너에서 직접 브라우저 실행</li>
</ol>



<p>Chromium Sidecar는 Helm Chart에서 자동 생성되지만, Playwright 바이너리는 <strong>Init Container</strong>로 별도 설치가 필요했습니다.</p>



<pre class="wp-block-code"><code>initContainers:
  install-browser:
    image:
      repository: ghcr.io/openclaw/openclaw
      tag: "2026.2.3"
    command:
      - sh
      - -c
      - |
        PLAYWRIGHT_BROWSERS_PATH=/home/node/.openclaw/browsers \
        node /app/node_modules/playwright-core/cli.js install chromium
</code></pre>



<h3 class="wp-block-heading">Step 7: DevOps 도구 설치 (kubectl, helm)</h3>



<p>OpenClaw Pod 안에서 클러스터를 관리하려면 kubectl과 helm이 필요합니다. 이들도 Init Container로 설치했습니다.</p>



<pre class="wp-block-code"><code>initContainers:
  install-tools:
    image:
      repository: alpine
      tag: "3.21"
    command:
      - sh
      - -c
      - |
        # kubectl 설치
        wget -q "https://dl.k8s.io/release/v1.30.4/bin/linux/amd64/kubectl" \
          -O /home/node/.openclaw/bin/kubectl
        chmod +x /home/node/.openclaw/bin/kubectl

        # helm 설치
        wget -q "https://get.helm.sh/helm-v3.17.1-linux-amd64.tar.gz" \
          -O /tmp/helm.tar.gz
        tar -xzf /tmp/helm.tar.gz -C /tmp
        mv /tmp/linux-amd64/helm /home/node/.openclaw/bin/helm
</code></pre>



<p>이 바이너리들은 PVC에 저장되므로, Pod가 재시작되어도 다시 다운로드할 필요가 없습니다.</p>



<h3 class="wp-block-heading">Step 8: RBAC 설정 &#8211; Pod에서 클러스터 제어하기</h3>



<p>Kubernetes에서 Pod가 클러스터 API에 접근하려면 <strong>ServiceAccount + ClusterRole + ClusterRoleBinding</strong>이 필요합니다.</p>



<pre class="wp-block-code"><code>apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: openclaw-cluster-role
rules:
  - apiGroups: &#091;""]
    resources: &#091;"nodes", "pods", "pods/log", "services",
                "configmaps", "secrets", "namespaces"]
    verbs: &#091;"get", "list", "watch", "create", "update", "patch", "delete"]
  - apiGroups: &#091;"apps"]
    resources: &#091;"deployments", "replicasets", "statefulsets"]
    verbs: &#091;"get", "list", "watch", "create", "update", "patch", "delete"]
  - apiGroups: &#091;"networking.k8s.io"]
    resources: &#091;"ingresses"]
    verbs: &#091;"get", "list", "watch", "create", "update", "patch", "delete"]
</code></pre>



<p>이렇게 설정하면 OpenClaw이 Pod 내부에서 <code>kubectl get nodes</code>, <code>kubectl get pods -A</code> 등을 자유롭게 실행할 수 있습니다.</p>



<figure class="wp-block-image size-full is-resized"><img loading="lazy" decoding="async" width="880" height="1136" src="https://blog.kwt.co.kr/wp-content/uploads/2026/02/image-3.png" alt="쿠버네티스를 점령한 openclaw 가재상" class="wp-image-1468" style="width:391px;height:auto"/></figure>



<h3 class="wp-block-heading">Step 9: 시스템 프롬프트 주입 (Bootstrap File)</h3>



<p>OpenClaw이 DevOps 작업을 정확히 수행하려면, 프로젝트 구조와 CI/CD 파이프라인에 대한 지식이 필요합니다.</p>



<p>처음에는 에이전트 설정에 <code>systemPrompt</code> 필드를 직접 추가했으나, <strong>&#8220;Unrecognized key&#8221; 오류로 Pod가 크래시</strong>했습니다. OpenClaw은 시스템 프롬프트를 설정 파일이 아닌 <strong>Bootstrap 파일</strong>로 주입하는 방식을 사용합니다.</p>



<p>Workspace 디렉토리에 <code>AGENTS.md</code> 파일을 생성하면, OpenClaw이 자동으로 이를 감지하여 시스템 프롬프트에 포함시킵니다.</p>



<pre class="wp-block-code"><code>/home/node/.openclaw/workspace/AGENTS.md
</code></pre>



<p>이 파일에 다음 정보를 포함했습니다:</p>



<ul class="wp-block-list">
<li>도구 경로 (kubectl, helm, git)</li>



<li>클러스터 정보 (노드 구성, StorageClass, Registry)</li>



<li>GitHub 인증 방법</li>



<li>전체 서비스 목록과 도메인 매핑</li>



<li>Jenkins API 사용법 (빌드 트리거, 상태 확인, Job 생성)</li>



<li>Blue-Green 배포 파이프라인 구조</li>



<li>deploy-config YAML 템플릿</li>
</ul>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">주요 설정 파일 구조</h2>



<h3 class="wp-block-heading">values.yaml (Helm Values)</h3>



<p>최종 <code>values.yaml</code>의 핵심 구조는 다음과 같습니다:</p>



<pre class="wp-block-code"><code>configMode: overwrite

app-template:
  controllers:
    main:
      serviceAccount:
        name: openclaw-sa          # RBAC 연결
      containers:
        main:
          envFrom:
            - secretRef:
                name: openclaw-env-secret  # API 키, 토큰
          env:
            PATH: /home/node/.openclaw/bin:...  # kubectl, helm 경로

      initContainers:
        install-browser: ...       # Playwright Chromium
        install-tools: ...         # kubectl, helm + AGENTS.md

  configMaps:
    config:
      data:
        openclaw.json: |
          {
            "gateway": { ... },
            "browser": { ... },
            "agents": {
              "defaults": {
                "workspace": "/home/node/.openclaw/workspace",
                "model": { "primary": "zai/glm-4.7" }
              }
            },
            "channels": {
              "slack": { "enabled": true, ... }
            }
          }

  persistence:
    data:
      type: persistentVolumeClaim
      size: 5Gi
      storageClass: longhorn
</code></pre>



<h3 class="wp-block-heading">Secret 구성</h3>



<pre class="wp-block-code"><code># openclaw-env-secret에 포함된 키들
ZAI_API_KEY: ...          # GLM 4.7 API 키
GATEWAY_TOKEN: ...        # Gateway 인증 토큰
SLACK_APP_TOKEN: ...      # Slack Socket Mode
SLACK_BOT_TOKEN: ...      # Slack Bot
GITHUB_TOKEN: ...         # GitHub PAT (코드 push)
JENKINS_URL: ...          # Jenkins API URL
JENKINS_USER: ...         # Jenkins 사용자
JENKINS_TOKEN: ...        # Jenkins API 토큰
</code></pre>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">OpenClaw이 할 수 있는 일</h2>



<p>구축이 완료된 후, OpenClaw은 Slack 메시지 하나로 다음 작업들을 수행할 수 있습니다:</p>



<h3 class="wp-block-heading">1. 클러스터 모니터링</h3>



<pre class="wp-block-code"><code>사용자: "현재 corpbreak-dev 네임스페이스 Pod 상태 확인해줘"
OpenClaw: kubectl get pods -n corpbreak-dev 실행 → 결과 보고
</code></pre>



<h3 class="wp-block-heading">2. 코드 작성 및 GitHub Push</h3>



<pre class="wp-block-code"><code>사용자: "exchange-service에 새 API 엔드포인트 추가해줘"
OpenClaw: git clone → 코드 수정 → git commit → git push
</code></pre>



<h3 class="wp-block-heading">3. Jenkins 빌드 트리거</h3>



<pre class="wp-block-code"><code>사용자: "checklist 서비스 dev 환경에 배포해줘"
OpenClaw: Jenkins API 호출 → 빌드 트리거 → 상태 모니터링
</code></pre>



<h3 class="wp-block-heading">4. 새 서비스 생성 (E2E)</h3>



<pre class="wp-block-code"><code>사용자: "새로운 survey-service를 만들어줘"
OpenClaw:
  1. 스켈레톤 프로젝트 클론
  2. 패키지명/설정 변경
  3. deploy-config YAML 작성
  4. GitHub에 Push
  5. Jenkins Job 생성
  6. 빌드 트리거 및 배포 확인
</code></pre>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">Claude Code와 함께한 구현 과정</h2>



<p>이번 구축의 특별한 점은, 전체 과정을 <strong>Claude Code</strong>와 실시간으로 협업하며 진행했다는 것입니다.</p>



<p>Claude Code는 Anthropic이 만든 CLI 기반 AI 코딩 도구로, 터미널에서 직접 파일을 읽고, 수정하고, 명령어를 실행할 수 있습니다. 이번 작업에서 Claude Code가 수행한 역할:</p>



<ul class="wp-block-list">
<li><strong>리서치</strong>: OpenClaw 설치 방법 웹 검색 및 문서 분석</li>



<li><strong>설정 파일 작성</strong>: values.yaml, RBAC, Ingress 등 모든 K8s 매니페스트 생성</li>



<li><strong>Helm 명령 실행</strong>: <code>helm install</code>, <code>helm upgrade</code> 직접 실행</li>



<li><strong>트러블슈팅</strong>: Pod 로그 분석, 에러 원인 파악, 설정 수정</li>



<li><strong>검증</strong>: <code>kubectl exec</code>로 Pod 내부 확인, API 테스트</li>
</ul>



<p><strong>사실상 Claude Code가 모든 것을 만들었다</strong>..!</p>



<p>총 <strong>10번의 Helm revision</strong>을 거치며, 각 단계에서 발생한 오류를 Claude Code가 실시간으로 진단하고 수정했습니다. 특히 &#8220;systemPrompt 필드가 인식되지 않는 문제&#8221;처럼 공식 문서에도 명확히 나와있지 않은 이슈를 웹 검색과 문서 분석을 통해 해결한 과정이 인상적이었습니다.</p>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">삽질 기록: 이것만은 알고 시작하세요</h2>



<p>구축 과정에서 겪은 주요 실수들을 정리합니다:</p>



<h3 class="wp-block-heading">1. bjw-s app-template의 ServiceAccount 설정</h3>



<p>OpenClaw Helm 차트는 내부적으로 <strong>bjw-s app-template</strong> 차트를 사용합니다. ServiceAccount를 연결할 때 일반적인 <code>spec.serviceAccountName</code>이 아닌, 차트 고유의 경로를 사용해야 합니다.</p>



<pre class="wp-block-code"><code># 틀린 방법들
defaultPodOptions:
  serviceAccountName: openclaw-sa  # Error: additional properties not allowed

serviceAccount:
  name: openclaw-sa  # Error: got string, want object

# 정답
controllers:
  main:
    serviceAccount:
      name: openclaw-sa
</code></pre>



<h3 class="wp-block-heading">2. OpenClaw 에이전트 설정</h3>



<p>에이전트 설정을 위해서는 workspace의 Bootstrap 파일(<code>AGENTS.md</code>, <code>SOUL.md</code> 등)을 사용해야 합니다.</p>



<h3 class="wp-block-heading">3. nginx-ingress의 configuration-snippet 차단</h3>



<p>nginx-ingress v1.11.2부터 보안상 <code>configuration-snippet</code> annotation이 기본 차단됩니다. WebSocket 지원은 <code>proxy-http-version: "1.1"</code> annotation만으로 충분합니다.</p>



<h3 class="wp-block-heading">4. Alpine에서 git 바이너리 복사 불가</h3>



<p>Alpine Linux에서 설치한 git은 공유 라이브러리에 의존하므로, 바이너리만 복사하면 동작하지 않습니다. 다행히 OpenClaw 이미지에는 <code>/usr/bin/git</code>이 이미 포함되어 있었습니다.</p>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">보안 고려사항</h2>



<p>AI 에이전트에게 클러스터 접근 권한을 부여할 때는 보안에 특별히 주의해야 합니다:</p>



<ul class="wp-block-list">
<li><strong>최소 권한 원칙</strong>: 필요한 리소스와 동작만 RBAC으로 허용</li>



<li><strong>프로덕션 보호</strong>: 시스템 프롬프트에 &#8220;prod 배포는 반드시 사용자 확인 후 진행&#8221; 명시</li>



<li><strong>Secret 관리</strong>: API 키와 토큰은 모두 Kubernetes Secret으로 관리</li>



<li><strong>감사 추적</strong>: OpenClaw의 로깅 설정으로 모든 도구 실행 기록 보관</li>



<li><strong>네트워크 격리</strong>: OpenClaw이 접근할 수 있는 외부 엔드포인트 제한 고려</li>
</ul>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">마치며</h2>



<p>AI 에이전트가 실제 인프라를 관리하는 시대가 이미 시작되었습니다. OpenClaw은 아직 초기 단계의 프로젝트이지만, Kubernetes 클러스터 위에서 실제 DevOps 작업을 수행할 수 있다는 가능성을 보여주었습니다.</p>



<p>물론 아직 개선할 점이 있습니다. 브라우저 스크린샷 기능이 완벽하지 않고, 복잡한 멀티스텝 작업에서 간혹 컨텍스트를 놓치기도 합니다. 하지만 &#8220;Slack으로 메시지 하나 보내면 서비스가 배포된다&#8221;는 경험은 충분히 인상적입니다.</p>



<p>단, 이미 충분히 지식이 있고, Claude Code 를 이용한 자동화를 구축해둔 상황이라면, 굳이 OpenClaw 를 설치해서 사용할 필요가 있나? 싶은 생각도 들었습니다. (아직 사용을 제대로 해보지 못해서 그런 것인지.. 맥미니가 다시 중고로 쏟아지지 않을까.!?)</p>



<p>이번 구축 과정에서 <strong>Claude Code</strong>의 역할도 매우 컸습니다. Helm 차트 분석, YAML 설정 작성, 실시간 트러블슈팅까지 &#8211; AI와 함께 AI를 설치하는 재미있는 경험이었습니다.</p>



<p>혹시 비슷한 환경을 구축해보고 싶으신 분이 계시다면, 이 글이 시행착오를 줄이는 데 도움이 되길 바랍니다.</p>



<h3 class="wp-block-heading">참고 링크</h3>



<ul class="wp-block-list">
<li>OpenClaw GitHub: https://github.com/openclaw/openclaw</li>



<li>OpenClaw Helm Chart: https://github.com/serhanekicii/openclaw-helm</li>



<li>OpenClaw 공식 문서: https://docs.openclaw.ai</li>



<li>Claude Code: https://claude.com/claude-code</li>
</ul>



<p></p>
		<div class="wpulike wpulike-robeen " ><div class="wp_ulike_general_class wp_ulike_is_restricted"><button type="button"
					aria-label="Like Button"
					data-ulike-id="1461"
					data-ulike-nonce="fcdebd7d9e"
					data-ulike-type="post"
					data-ulike-template="wpulike-robeen"
					data-ulike-display-likers=""
					data-ulike-likers-style="popover"
					class="wp_ulike_btn wp_ulike_put_image wp_post_btn_1461"></button><span class="count-box wp_ulike_counter_up" data-ulike-counter-value="0"></span>			</div></div>
	<p>The post <a href="https://blog.kwt.co.kr/openclaw-kubernetes-%ed%81%b4%eb%9f%ac%ec%8a%a4%ed%84%b0%ec%97%90-%ea%b5%ac%ec%b6%95%ed%95%98%ea%b8%b0/">OpenClaw &#8211; Kubernetes 클러스터에 구축기 with Claude Code</a> appeared first on <a href="https://blog.kwt.co.kr"></a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.kwt.co.kr/openclaw-kubernetes-%ed%81%b4%eb%9f%ac%ec%8a%a4%ed%84%b0%ec%97%90-%ea%b5%ac%ec%b6%95%ed%95%98%ea%b8%b0/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>MySQL Operator 로 운영하는 InnoDB Cluster</title>
		<link>https://blog.kwt.co.kr/mysql-operator-%eb%a1%9c-%ec%9a%b4%ec%98%81%ed%95%98%eb%8a%94-innodb-cluster/</link>
					<comments>https://blog.kwt.co.kr/mysql-operator-%eb%a1%9c-%ec%9a%b4%ec%98%81%ed%95%98%eb%8a%94-innodb-cluster/#respond</comments>
		
		<dc:creator><![CDATA[시간 조절자]]></dc:creator>
		<pubDate>Sun, 03 Nov 2024 08:51:54 +0000</pubDate>
				<category><![CDATA[기술]]></category>
		<category><![CDATA[쿠버네티스]]></category>
		<category><![CDATA[InnoDB Cluster]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[MySQL Operator]]></category>
		<category><![CDATA[어렵네]]></category>
		<guid isPermaLink="false">https://kwt.co.kr/?p=844</guid>

					<description><![CDATA[<p>MySQL Operator 라는 생소한 친구가 있었다. 요즘 모두가 울부짖는 고가용성(HA, High Availability)을 위해 InnoDB Cluster가 나왔고, 이 클러스터를 더 편하게 운영하기 위해 MySQL Operator가 나왔다. 마지막 근무했던 회사는 DB팀이 있어서 DB관리가 개발자의 영역이 아니었기 때문에 테이블 설계, 쿼리 생성할 때 DB팀의 검토를 받고 진행 되었다. 개발자 입장에서 보면 상당히 편하지만 그만큼 DB 기술 발전에 둔감해졌고, [&#8230;]</p>
<p>The post <a href="https://blog.kwt.co.kr/mysql-operator-%eb%a1%9c-%ec%9a%b4%ec%98%81%ed%95%98%eb%8a%94-innodb-cluster/">MySQL Operator 로 운영하는 InnoDB Cluster</a> appeared first on <a href="https://blog.kwt.co.kr"></a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>MySQL Operator 라는 생소한 친구가 있었다.</p>



<p>요즘 모두가 울부짖는 <a href="https://kwt.co.kr/%ea%b3%a0%ea%b0%80%ec%9a%a9%ec%84%b1/">고가용성(HA, High Availability)</a>을 위해 InnoDB Cluster가 나왔고, 이 클러스터를 더 편하게 운영하기 위해 MySQL Operator가 나왔다.</p>



<p>마지막 근무했던 회사는 DB팀이 있어서 DB관리가 개발자의 영역이 아니었기 때문에 테이블 설계, 쿼리 생성할 때 DB팀의 검토를 받고 진행 되었다.</p>



<p>개발자 입장에서 보면 상당히 편하지만 그만큼 DB 기술 발전에 둔감해졌고, 이런 기능이 있다는 것을 이번에 구축하며 처음 알게됐다.</p>



<p>이번에 알게된 내용과 한참을 고생시킨 문제를 기록한다.</p>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>





<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">InnoDB Cluster</h2>



<p>고가용성을 위한 MySQL 서버를 구성하는 방법이다. MySQL 5.7 버전부터 사용 가능하다고 한다. 처음에 나는 이것이 master-slave replication 이라고 생각했는데, 엄연히 다른 개념으로 봐야 맞다.</p>



<p>사용되는 방법은 상당히 유사하지만 가장 큰 차이점은 자동 장애 조치(Failover) 지원 유무이다.</p>



<p>master-slave replication의 경우 master의 장애가 발생 할 경우 사람이 달라 붙어 slave를 master로 사용하도록 하거나, master 노드를 복구해야만 한다. InnoDB Cluster 의 경우 primary 의 장애가 발생했을 때, secondary가 자동으로 primary로 선출되어 장애를 자동으로 극복한다.</p>



<p>물론 primary 에서 장애가 발생 했다는 것은 높은 확률로 많은 트래픽에 의해서 발생한 것일테니, 서버 한대가 빠진 마당에 자동 복구가 되어도 다시 장애가 발생 할 가능성이 높겠지만. 트래픽에 의한 장애가 아닐 경우 이런 자동 복구 기능은 고가용성에 필수적 요소이다.</p>



<p></p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>항목</th><th>Master-Slave Replication</th><th>InnoDB Cluster</th></tr></thead><tbody><tr><td>구조</td><td>Master와 다수의 Slave로 구성</td><td>Primary와 다수의 Secondary로 구성</td></tr><tr><td>복제 방식</td><td>비동기 복제 (Asynchronous Replication)</td><td>동기 복제 (Synchronous) 또는 반동기 복제 (Semi-Synchronous)</td></tr><tr><td>고가용성 (HA)</td><td>수동 장애 조치 (관리자가 수동으로 처리)</td><td>자동 장애 조치 (Failover) 지원</td></tr><tr><td>읽기 확장성</td><td>다수의 Slave를 통해 읽기 확장 가능</td><td>Secondary 노드를 통해 읽기 확장 가능</td></tr><tr><td>쓰기 확장성</td><td>Master 노드에서만 쓰기 가능 (한계 있음)</td><td>다중 Primary 구성을 통해 쓰기 확장 가능</td></tr><tr><td>자동 장애 복구</td><td>없음, 수동으로 Slave를 승격해야 함</td><td>Primary 노드 장애 시 자동으로 새로운 Primary로 승격</td></tr><tr><td>일관성</td><td>데이터 일관성이 약함 (복제 지연 발생 가능)</td><td>강한 일관성 보장 (동기화된 데이터 복제)</td></tr><tr><td>데이터 유실 가능성</td><td>Master 장애 시 일부 데이터 유실 가능성 있음</td><td>동기 복제를 통해 데이터 유실 최소화</td></tr><tr><td>구성 복잡도</td><td>비교적 간단</td><td>다소 복잡 (Group Replication, MySQL Router 등)</td></tr><tr><td>읽기 및 쓰기 처리</td><td>Master(읽기/쓰기), Slave(읽기)</td><td>Primary(읽기/쓰기), Secondary(읽기)</td></tr><tr><td>장애 조치 시간</td><td>관리자가 개입해야 하므로 장애 조치에 시간이 소요됨</td><td>자동 장애 조치로 신속한 복구 가능</td></tr><tr><td>데이터 충돌 관리</td><td>충돌 관리 필요 없음 (Master가 유일한 쓰기 노드)</td><td>다중 Primary 구성 시 충돌 관리 필요</td></tr></tbody></table></figure>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">MySQL Operator</h2>



<p>Kubernetes 내에서 운영되는 MySQL InnoDB Cluster를 운영하기 위해 존재하는 친구이다.</p>



<p>많은 곳에서 설명한 자료를 보면 공통적인 부분은 &#8216;Operator Pattern 을 따른다&#8217; 는 것.</p>



<p>이놈의 패턴이라는 용어는 전방위로 사용되는데 대략 &#8216;자주 발생하는 문제에 대한 재사용 가능한 해결 방법&#8217; 정도로 받아 들이면 된다.</p>



<p>문제가 있을 때 대략 이런 파훼법이 있다는 것을 앞선 개발자가 경험하고, 해결하기 위해 범용적으로 적용 가능한 솔루션이라고 봐도 될듯 싶다.</p>



<p>이 패턴이라는 것이 대단히 복잡한 것은 아니고, 아주 후려쳐서 얘기하면</p>



<p>운영자가 설정한 특정 상태를 저장해놓고, 이 상태가 잘 유지되고 있는지 계속 감시하는 놈(Operator)이 있다.</p>



<p>감시하다가 문제가 생겼다고 판단하면 잽싸게 최초 설정된 상태를 유지하기 위해 고용된 이 감시하는 놈이 있는 것 자체를 Operator 패턴이라고 한다.</p>



<figure class="wp-block-image size-full is-resized"><img loading="lazy" decoding="async" width="700" height="525" src="https://blog.kwt.co.kr/wp-content/uploads/2024/11/감시.jpg" alt="" class="wp-image-845" style="width:384px;height:auto"/></figure>



<p>Kubernetes 자체에서도 컨트롤러, kubelet이 서비스의 상태를 감지하고 문제가 발생하면 파드를 재시작 한다던가, 사용량을 보고 파드의 수를 조절하는 기본적인 관리가 된다.</p>



<p>하지만 우리가 원하는건 좀 더 우아한 기능으로 감시자 하나를 추가로 고용한 것이다.</p>



<p>Primary에 장애가 나면 Secondary를 승격하거나, 데이터 일관성을 유지하거나 하는 좀 더 고급 기능을 수행한다. 마치 기성복과 맞춤복 같은 느낌이랄까.</p>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">눈물겨운 설치 과정</h2>



<p>정상 동작을 하도록 설치하는데 꼬박 3일이 걸렸다.</p>



<p>설치 방법은 수도 없이 많은 곳에 널려있고, 내 뒤엔 GPT 형님과 클로드 형님도 있거늘. 무엇이 문제였던가?</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">설치 방법</h3>



<p>MySQL 오퍼레이터 설치</p>



<pre class="wp-block-code"><code># MySQL 오퍼레이터 Helm 저장소 추가
helm repo add mysql-operator &lt;https://mysql.github.io/mysql-operator/&gt;
helm repo update

# MySQL 오퍼레이터 설치
helm install mysql-operator mysql-operator/mysql-operator \\
   --namespace mysql-operator --create-namespace
</code></pre>



<p>Custom Resource</p>



<pre class="wp-block-code"><code>apiVersion: mysql.oracle.com/v2
kind: InnoDBCluster
metadata:
  name: mysql-cluster
  namespace: mysql-cluster
spec:
  secretName: mypwds
  tlsUseSelfSigned: true
  instances: 3
  version: 9.1.0
  router:
    instances: 1
    version: 9.1.0
  datadirVolumeClaimTemplate:
    accessModes: 
      - ReadWriteOnce
    resources:
      requests:
        storage: 10Gi
    storageClassName: local-storage
    volumeMode: Filesystem
  mycnf: |
    &#091;mysqld]
    authentication_policy=mysql_native_password
    character-set-server=utf8mb4
    collation-server=utf8mb4_general_ci
    default-time-zone='+09:00'
    innodb_buffer_pool_size=1G
    innodb_file_per_table=1
    innodb_flush_log_at_trx_commit=1
    innodb_flush_method=O_DIRECT
    max_connections=100
    max_user_connections=40
    wait_timeout=600
    interactive_timeout=600</code></pre>



<p>위에서 사용 할 mypwds Secret 생성</p>



<pre class="wp-block-code"><code>kubectl create secret -n kwt-dev generic mypwds \\
        --from-literal=rootUser=root \\
        --from-literal=rootHost=% \\
        --from-literal=rootPassword="password"
</code></pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">과도한 형님 의존</h3>



<p>너무 믿었다. 형님들이 잘 해결해주리라는 믿음이 과했던 나머지, 처음부터 차근차근 짚고 생각하지 않았다.</p>



<pre class="wp-block-code"><code>2024-11-03T06:16:03.039436Z 1 &#091;Note] &#091;MY-010006] &#091;Server] Using data dictionary with version '90000'.
mysqld: Table 'mysql.plugin' doesn't exist
2024-11-03T06:16:03.043256Z 0 &#091;ERROR] &#091;MY-010735] &#091;Server] Could not open the mysql.plugin table. Please perform the MySQL upgrade procedure.
2024-11-03T06:16:03.046329Z 0 &#091;Warning] &#091;MY-010441] &#091;Server] Failed to open optimizer cost constant tables
2024-11-03T06:16:03.047803Z 0 &#091;Warning] &#091;MY-010441] &#091;Server] Failed to open optimizer cost constant tables
2024-11-03T06:16:03.051836Z 0 &#091;Note] &#091;MY-011332] &#091;Server] Plugin mysqlx reported: 'IPv6 is available'
2024-11-03T06:16:03.054390Z 0 &#091;Note] &#091;MY-011323] &#091;Server] Plugin mysqlx reported: 'X Plugin ready for connections. bind-address: '::' port: 33060'
2024-11-03T06:16:03.054494Z 0 &#091;Note] &#091;MY-011323] &#091;Server] Plugin mysqlx reported: 'X Plugin ready for connections. socket: '/var/run/mysqld/mysqlx.sock''
2024-11-03T06:16:03.082090Z 0 &#091;Note] &#091;MY-010902] &#091;Server] Thread priority attribute setting in Resource Group SQL shall be ignored due to unsupported platform or insufficient privilege.
2024-11-03T06:16:03.085056Z 0 &#091;Note] &#091;MY-010856] &#091;Server] Failed to open the crashed binlog file when source server is recovering it.
2024-11-03T06:16:03.097017Z 0 &#091;Note] &#091;MY-013911] &#091;Server] Crash recovery finished in binlog engine. No attempts to commit, rollback or prepare any transactions.
2024-11-03T06:16:03.097063Z 0 &#091;Note] &#091;MY-013911] &#091;Server] Crash recovery finished in InnoDB engine. No attempts to commit, rollback or prepare any transactions.
2024-11-03T06:16:03.101546Z 0 &#091;Note] &#091;MY-012487] &#091;InnoDB] DDL log recovery : begin
2024-11-03T06:16:03.101858Z 0 &#091;Note] &#091;MY-012488] &#091;InnoDB] DDL log recovery : end
2024-11-03T06:16:03.102062Z 0 &#091;Note] &#091;MY-011946] &#091;InnoDB] Loading buffer pool(s) from /var/lib/mysql/ib_buffer_pool
2024-11-03T06:16:03.102785Z 0 &#091;Note] &#091;MY-011946] &#091;InnoDB] Buffer pool(s) load completed at 241103  6:16:03
2024-11-03T06:16:03.105014Z 0 &#091;Warning] &#091;MY-010015] &#091;Repl] Gtid table is not ready to be used. Table 'mysql.gtid_executed' cannot be opened.
2024-11-03T06:16:03.105331Z 0 &#091;Note] &#091;MY-012922] &#091;InnoDB] Waiting for purge to start
2024-11-03T06:16:03.175308Z 0 &#091;Warning] &#091;MY-010015] &#091;Repl] Gtid table is not ready to be used. Table 'mysql.gtid_executed' cannot be opened.
2024-11-03T06:16:03.177100Z 0 &#091;Note] &#091;MY-010182] &#091;Server] Found ca.pem, server-cert.pem and server-key.pem in data directory. Trying to enable SSL support using them.
2024-11-03T06:16:03.179367Z 0 &#091;Note] &#091;MY-010304] &#091;Server] Skipping generation of SSL certificates as certificate files are present in data directory.
2024-11-03T06:16:03.182931Z 0 &#091;Warning] &#091;MY-010068] &#091;Server] CA certificate ca.pem is self signed.
2024-11-03T06:16:03.182962Z 0 &#091;System] &#091;MY-013602] &#091;Server] Channel mysql_main configured to support TLS. Encrypted connections are now supported for this channel.
2024-11-03T06:16:03.183324Z 0 &#091;Note] &#091;MY-010308] &#091;Server] Skipping generation of RSA key pair through --sha256_password_auto_generate_rsa_keys as key files are present in data directory.
2024-11-03T06:16:03.183617Z 0 &#091;Note] &#091;MY-010308] &#091;Server] Skipping generation of RSA key pair through --caching_sha2_password_auto_generate_rsa_keys as key files are present in data directory.
2024-11-03T06:16:03.185557Z 0 &#091;Note] &#091;MY-010252] &#091;Server] Server hostname (bind-address): '*'; port: 3306
2024-11-03T06:16:03.185603Z 0 &#091;Note] &#091;MY-010253] &#091;Server] IPv6 is available.
2024-11-03T06:16:03.185613Z 0 &#091;Note] &#091;MY-010264] &#091;Server]   - '::' resolves to '::';
2024-11-03T06:16:03.185625Z 0 &#091;Note] &#091;MY-010251] &#091;Server] Server socket created on IP: '::'.
2024-11-03T06:16:03.187716Z 0 &#091;Warning] &#091;MY-010441] &#091;Server] Failed to open optimizer cost constant tables
2024-11-03T06:16:03.187906Z 0 &#091;ERROR] &#091;MY-013129] &#091;Server] A message intended for a client cannot be sent there as no client-session is attached. Therefore, we're sending the information to the error-log instead: MY-001146 - Table 'mysql.component' doesn't exist
2024-11-03T06:16:03.187925Z 0 &#091;Warning] &#091;MY-013129] &#091;Server] A message intended for a client cannot be sent there as no client-session is attached. Therefore, we're sending the information to the error-log instead: MY-003543 - The mysql.component table is missing or has an incorrect definition.
2024-11-03T06:16:03.188047Z 0 &#091;Warning] &#091;MY-000067] &#091;Server] unknown variable 'loose_group_replication_recovery_use_ssl=1'.
2024-11-03T06:16:03.189278Z 0 &#091;ERROR] &#091;MY-010326] &#091;Server] Fatal error: Can't open and lock privilege tables: Table 'mysql.user' doesn't exist
2024-11-03T06:16:03.189328Z 0 &#091;ERROR] &#091;MY-010952] &#091;Server] The privilege system failed to initialize correctly. For complete instructions on how to upgrade MySQL to a new version please see the 'Upgrading MySQL' section from the MySQL manual.
2024-11-03T06:16:03.189418Z 0 &#091;ERROR] &#091;MY-010119] &#091;Server] Aborting
2024-11-03T06:16:03.190571Z 0 &#091;Note] &#091;MY-012330] &#091;InnoDB] FTS optimize thread exiting.
2024-11-03T06:16:04.191663Z 0 &#091;Note] &#091;MY-010120] &#091;Server] Binlog end
2024-11-03T06:16:04.194566Z 0 &#091;Note] &#091;MY-015019] &#091;Server] MySQL Server: Plugins Shutdown - start.
2024-11-03T06:16:04.194620Z 0 &#091;Note] &#091;MY-010733] &#091;Server] Shutting down plugin 'auth_socket'
2024-11-03T06:16:04.194639Z 0 &#091;Note] &#091;MY-010733] &#091;Server] Shutting down plugin 'mysqlx'
2024-11-03T06:16:04.195625Z 0 &#091;Note] &#091;MY-010733] &#091;Server] Shutting down plugin 'mysqlx_cache_cleaner'
2024-11-03T06:16:04.195666Z 0 &#091;Note] &#091;MY-010733] &#091;Server] Shutting down plugin 'ngram'
2024-11-03T06:16:04.195681Z 0 &#091;Note] &#091;MY-010733] &#091;Server] Shutting down plugin 'BLACKHOLE'
2024-11-03T06:16:04.195699Z 0 &#091;Note] &#091;MY-010733] &#091;Server] Shutting down plugin 'ARCHIVE'
2024-11-03T06:16:04.195715Z 0 &#091;Note] &#091;MY-010733] &#091;Server] Shutting down plugin 'TempTable'
2024-11-03T06:16:04.195732Z 0 &#091;Note] &#091;MY-010733] &#091;Server] Shutting down plugin 'PERFORMANCE_SCHEMA'
2024-11-03T06:16:04.195850Z 0 &#091;Note] &#091;MY-010733] &#091;Server] Shutting down plugin 'MRG_MYISAM'
2024-11-03T06:16:04.195881Z 0 &#091;Note] &#091;MY-010733] &#091;Server] Shutting down plugin 'MyISAM'
2024-11-03T06:16:04.195910Z 0 &#091;Note] &#091;MY-010733] &#091;Server] Shutting down plugin 'INNODB_SESSION_TEMP_TABLESPACES'
2024-11-03T06:16:04.195924Z 0 &#091;Note] &#091;MY-010733] &#091;Server] Shutting down plugin 'INNODB_CACHED_INDEXES'
2024-11-03T06:16:04.195935Z 0 &#091;Note] &#091;MY-010733] &#091;Server] Shutting down plugin 'INNODB_VIRTUAL'
2024-11-03T06:16:04.195961Z 0 &#091;Note] &#091;MY-010733] &#091;Server] Shutting down plugin 'INNODB_COLUMNS'
2024-11-03T06:16:04.195975Z 0 &#091;Note] &#091;MY-010733] &#091;Server] Shutting down plugin 'INNODB_TABLESPACES'
2024-11-03T06:16:04.195986Z 0 &#091;Note] &#091;MY-010733] &#091;Server] Shutting down plugin 'INNODB_INDEXES'
2024-11-03T06:16:04.195995Z 0 &#091;Note] &#091;MY-010733] &#091;Server] Shutting down plugin 'INNODB_TABLESTATS'
2024-11-03T06:16:04.196006Z 0 &#091;Note] &#091;MY-010733] &#091;Server] Shutting down plugin 'INNODB_TABLES'
2024-11-03T06:16:04.196016Z 0 &#091;Note] &#091;MY-010733] &#091;Server] Shutting down plugin 'INNODB_FT_INDEX_TABLE'
2024-11-03T06:16:04.196028Z 0 &#091;Note] &#091;MY-010733] &#091;Server] Shutting down plugin 'INNODB_FT_INDEX_CACHE'
2024-11-03T06:16:04.196039Z 0 &#091;Note] &#091;MY-010733] &#091;Server] Shutting down plugin 'INNODB_FT_CONFIG'
2024-11-03T06:16:04.196049Z 0 &#091;Note] &#091;MY-010733] &#091;Server] Shutting down plugin 'INNODB_FT_BEING_DELETED'
2024-11-03T06:16:04.196059Z 0 &#091;Note] &#091;MY-010733] &#091;Server] Shutting down plugin 'INNODB_FT_DELETED'
2024-11-03T06:16:04.196069Z 0 &#091;Note] &#091;MY-010733] &#091;Server] Shutting down plugin 'INNODB_FT_DEFAULT_STOPWORD'
2024-11-03T06:16:04.196089Z 0 &#091;Note] &#091;MY-010733] &#091;Server] Shutting down plugin 'INNODB_METRICS'
2024-11-03T06:16:04.196099Z 0 &#091;Note] &#091;MY-010733] &#091;Server] Shutting down plugin 'INNODB_TEMP_TABLE_INFO'
2024-11-03T06:16:04.196109Z 0 &#091;Note] &#091;MY-010733] &#091;Server] Shutting down plugin 'INNODB_BUFFER_POOL_STATS'
2024-11-03T06:16:04.196120Z 0 &#091;Note] &#091;MY-010733] &#091;Server] Shutting down plugin 'INNODB_BUFFER_PAGE_LRU'
2024-11-03T06:16:04.196139Z 0 &#091;Note] &#091;MY-010733] &#091;Server] Shutting down plugin 'INNODB_BUFFER_PAGE'
2024-11-03T06:16:04.196150Z 0 &#091;Note] &#091;MY-010733] &#091;Server] Shutting down plugin 'INNODB_CMP_PER_INDEX_RESET'
2024-11-03T06:16:04.196160Z 0 &#091;Note] &#091;MY-010733] &#091;Server] Shutting down plugin 'INNODB_CMP_PER_INDEX'
2024-11-03T06:16:04.196170Z 0 &#091;Note] &#091;MY-010733] &#091;Server] Shutting down plugin 'INNODB_CMPMEM_RESET'
2024-11-03T06:16:04.196180Z 0 &#091;Note] &#091;MY-010733] &#091;Server] Shutting down plugin 'INNODB_CMPMEM'
2024-11-03T06:16:04.196190Z 0 &#091;Note] &#091;MY-010733] &#091;Server] Shutting down plugin 'INNODB_CMP_RESET'
2024-11-03T06:16:04.196200Z 0 &#091;Note] &#091;MY-010733] &#091;Server] Shutting down plugin 'INNODB_CMP'
2024-11-03T06:16:04.196211Z 0 &#091;Note] &#091;MY-010733] &#091;Server] Shutting down plugin 'INNODB_TRX'
2024-11-03T06:16:04.196235Z 0 &#091;Note] &#091;MY-010733] &#091;Server] Shutting down plugin 'InnoDB'
2024-11-03T06:16:04.196316Z 0 &#091;Note] &#091;MY-013072] &#091;InnoDB] Starting shutdown...
2024-11-03T06:16:04.197213Z 0 &#091;Note] &#091;MY-011944] &#091;InnoDB] Dumping buffer pool(s) to /var/lib/mysql/ib_buffer_pool
2024-11-03T06:16:04.197631Z 0 &#091;Note] &#091;MY-011944] &#091;InnoDB] Buffer pool(s) dump completed at 241103  6:16:04
2024-11-03T06:16:04.210337Z 0 &#091;Note] &#091;MY-013084] &#091;InnoDB] Log background threads are being closed...
2024-11-03T06:16:04.751315Z 0 &#091;Note] &#091;MY-013854] &#091;InnoDB] Bytes written to disk by DBLWR (ON): 671744
2024-11-03T06:16:04.751886Z 0 &#091;Note] &#091;MY-012980] &#091;InnoDB] Shutdown completed; log sequence number 9457587
2024-11-03T06:16:04.753934Z 0 &#091;Note] &#091;MY-012255] &#091;InnoDB] Removed temporary tablespace data file: "ibtmp1"
2024-11-03T06:16:04.753973Z 0 &#091;Note] &#091;MY-010733] &#091;Server] Shutting down plugin 'MEMORY'
2024-11-03T06:16:04.753983Z 0 &#091;Note] &#091;MY-010733] &#091;Server] Shutting down plugin 'CSV'
2024-11-03T06:16:04.753991Z 0 &#091;Note] &#091;MY-010733] &#091;Server] Shutting down plugin 'daemon_keyring_proxy_plugin'
2024-11-03T06:16:04.754012Z 0 &#091;Note] &#091;MY-010733] &#091;Server] Shutting down plugin 'sha2_cache_cleaner'
2024-11-03T06:16:04.754019Z 0 &#091;Note] &#091;MY-010733] &#091;Server] Shutting down plugin 'caching_sha2_password'
2024-11-03T06:16:04.754028Z 0 &#091;Note] &#091;MY-010733] &#091;Server] Shutting down plugin 'sha256_password'
2024-11-03T06:16:04.754788Z 0 &#091;Note] &#091;MY-010733] &#091;Server] Shutting down plugin 'binlog'
2024-11-03T06:16:04.754816Z 0 &#091;Note] &#091;MY-015020] &#091;Server] MySQL Server: Plugins Shutdown - end.
2024-11-03T06:16:04.755458Z 0 &#091;Note] &#091;MY-015021] &#091;Server] MySQL Server: Components Shutdown - start.
2024-11-03T06:16:04.756532Z 0 &#091;Note] &#091;MY-015022] &#091;Server] MySQL Server: Components Shutdown - end (with return value = 0).
2024-11-03T06:16:04.756643Z 0 &#091;System] &#091;MY-010910] &#091;Server] /usr/sbin/mysqld: Shutdown complete (mysqld 9.1.0)  MySQL Community Server - GPL.
2024-11-03T06:16:04.756651Z 0 &#091;System] &#091;MY-015016] &#091;Server] MySQL Server - end.</code></pre>



<p>나를 지독하게 괴롭혔던 친구.</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p>Q. 형님. 뭐가 문제입니까?</p>



<p>A. 이렇게 바꿔보거라~</p>



<p></p>



<p>무지하게 바꾸고 재설치를 진행했지만 결국 돌고 돌아 같은 오류가 반복되었다. 또는 형님이 알려준 설정 자체가 맞지 않는 경우도 자주 발생했다.</p>



<div style="height:21px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">버전이 문제더라</h3>



<p>혹시나 하는 마음에 8.0.40 버전의 안정화된 MySQL은 제대로 동작하지 않을까? 싶어 설치해봤다.</p>



<p>여태까지 무슨 일이 있었냐는 듯 설치가 되어버렸다.</p>



<p>형님들이랑 수 없이 떠들고 구글링 했던 시간이 주마등처럼 스처가며 허무함이 몰려왔다.</p>



<p>이걸 쓰는 시점에 버전에 따른 버그인지는 확실하지 않다. 이렇게 쉽게 해결될 문제를 3일간 괴로워하며 똑같은 작업을 반복했던 자신에게 화날 뿐.</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="420" height="298" src="https://blog.kwt.co.kr/wp-content/uploads/2024/11/무한반복.jpg" alt="" class="wp-image-848"/></figure>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">고민했던 포인트</h2>



<ol class="wp-block-list">
<li>MySQL 별도의 네임스페이스를 만들 것인가?<br>지금 만드는 서비스는 포트폴리오에 사용될 것이고 나 혼자 관리할 것이기에 네임스페이스를 많이 나눌 필요가 없다고 생각했다.<br>어플리케이션과 같은 네임스페이스를 공유하자.</li>



<li>Helm 차트를 이용해서 설치 할 것인가?<br>MySQL Operator 는 Helm차트를 이용해서 설치해도 문제가 없다고 생각했다. 특별히 별도의 설정을 하지 않아도 될 것으로 생각했기 때문.<br>InnoDB Cluster 는 manifest 설치를 선택했는데, 내가 원하는 설정으로 Stateful Set 을 설치해야 했기 때문이다.</li>



<li>Longhorn Storage Class를 이용할 것인가?<br>InnoDB Cluster는 이미 복제본을 여러 노드에 생성하기 때문에, 중복된 기능을 제공하는 분산 스토리지 솔루션을 굳이 이용할 필요가 없다고 생각됐다. 각 노드에 저장되는 Local Storage를 선택했다.</li>
</ol>



<p></p>
		<div class="wpulike wpulike-robeen " ><div class="wp_ulike_general_class wp_ulike_is_restricted"><button type="button"
					aria-label="Like Button"
					data-ulike-id="844"
					data-ulike-nonce="dfcc734a60"
					data-ulike-type="post"
					data-ulike-template="wpulike-robeen"
					data-ulike-display-likers=""
					data-ulike-likers-style="popover"
					class="wp_ulike_btn wp_ulike_put_image wp_post_btn_844"></button><span class="count-box wp_ulike_counter_up" data-ulike-counter-value="0"></span>			</div></div>
	<p>The post <a href="https://blog.kwt.co.kr/mysql-operator-%eb%a1%9c-%ec%9a%b4%ec%98%81%ed%95%98%eb%8a%94-innodb-cluster/">MySQL Operator 로 운영하는 InnoDB Cluster</a> appeared first on <a href="https://blog.kwt.co.kr"></a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.kwt.co.kr/mysql-operator-%eb%a1%9c-%ec%9a%b4%ec%98%81%ed%95%98%eb%8a%94-innodb-cluster/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
