Ansible로 쿠버네티스 환경 구축 시 발생하는 문제점들과 해결 방법 🚀
안녕하세요! 오늘은 Ansible을 사용해 쿠버네티스 환경을 구축하면서 만났던 여러 가지 문제점들과 그 해결 방법에 대해 이야기해보려고 해요. 특히 단일 노드 클러스터에서 파드 스케줄링, 자원 관리, 그리고 YAML 문법 관련 이슈들을 중심으로 알아볼게요! 😊
테인트(Taint)와 톨러레이션(Toleration)의 이해 🧩
먼저 쿠버네티스의 중요한 개념인 테인트와 톨러레이션에 대해 알아볼까요?
테인트란? 🚫
테인트는 “이 노드에는 특정 조건을 만족하는 파드만 스케줄링할 거야!”라고 노드에 표시하는 일종의 ‘접근 제한’ 기능이에요. 주로 특수한 목적을 가진 노드(예: GPU 노드, 마스터 노드)를 보호하기 위해 사용합니다.
# 노드에 테인트 적용하기
kubectl taint nodes node1 key=value:NoSchedule
# 테인트 확인하기
kubectl describe node node1 | grep Taints
테인트는 세 가지 효과(Effect)를 가질 수 있어요:
- NoSchedule: 톨러레이션이 없는 파드는 절대 스케줄링되지 않음
- PreferNoSchedule: 가능하면 스케줄링하지 않지만, 클러스터 상황에 따라 허용할 수도 있음
- NoExecute: 기존에 실행 중인 파드도 제거됨
톨러레이션이란? 🛡️
톨러레이션은 “이 파드는 해당 테인트가 있는 노드에도 스케줄링될 수 있어!”라고 파드에 표시하는 기능이에요. 테인트가 노드의 ‘접근 제한’이라면, 톨러레이션은 파드의 ‘출입 허가증’인 셈이죠.
# 파드에 톨러레이션 추가하기
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx
tolerations:
- key: "key"
operator: "Equal"
value: "value"
effect: "NoSchedule"
마스터 노드의 테인트 🎮
쿠버네티스에서는 기본적으로 마스터 노드(컨트롤 플레인 노드)에 다음과 같은 테인트를 적용합니다:
node-role.kubernetes.io/control-plane:NoSchedule
node-role.kubernetes.io/master:NoSchedule (구버전)
이는 “일반 워크로드는 마스터 노드에 스케줄링하지 마!”라는 의미예요. 하지만 단일 노드 클러스터나 개발 환경에서는 이 테인트를 제거하거나 모든 파드에 톨러레이션을 추가해 마스터 노드도 활용하는 것이 좋습니다!
문제 1: 파드가 Pending 상태로 멈춰있어요! 🕐
물뮤(MoolMeow) 프로젝트를 위한 쿠버네티스 환경을 설정하면서 가장 먼저 만난 문제는 거의 모든 파드가 Pending 상태로 멈춰있는 현상이었어요.
$ kubectl get pods --all-namespaces
NAMESPACE NAME READY STATUS RESTARTS AGE
ingress-nginx ingress-nginx-admission-create-qpm8v 0/1 Pending 0 33m
metallb-system controller-5f56cd6f78-fghps 0/1 Pending 0 22h
metallb-system speaker-plpxf 0/1 Pending 0 22h
rook-ceph rook-ceph-operator-6dd77b4466-kv7pq 0/1 Pending 0 22h
이벤트 로그를 확인해보니 다음과 같은 메시지가 나타났어요:
0/1 nodes are available: 1 node(s) had untolerated taint {node-role.kubernetes.io/control-plane: }
원인 분석 🔍
이 문제는 우리의 클러스터가 단일 노드(마스터 노드만 있는) 구성인데, 마스터 노드에는 기본적으로 테인트가 설정되어 있어 일반 워크로드가 스케줄링되지 않았기 때문이었습니다.
해결 방법 1: 테인트 제거하기 ✂️
가장 간단한 해결책은 마스터 노드의 테인트를 제거하는 것입니다:
# Ansible 태스크
- name: Remove control-plane taint to allow pods to be scheduled on master
become: yes
become_user: vagrant
shell: |
kubectl taint nodes --all node-role.kubernetes.io/control-plane- || true
kubectl taint nodes --all node-role.kubernetes.io/master- || true
environment:
KUBECONFIG: /home/vagrant/.kube/config
주의할 점은 테인트 이름 뒤에 -
를 붙이면 해당 테인트를 제거한다는 의미입니다!
해결 방법 2: 톨러레이션 추가하기 🛠️
모든 컴포넌트에 톨러레이션을 추가하는 방법도 있습니다. Helm 차트를 사용할 경우 다음과 같이 설정할 수 있어요:
# Ansible 태스크 (Nginx Ingress 설치 예시)
- name: Install Nginx Ingress Controller
become: yes
become_user: vagrant
shell: |
helm install ingress-nginx ingress-nginx/ingress-nginx \
--namespace ingress-nginx \
--create-namespace \
--set controller.service.type=LoadBalancer \
--set controller.publishService.enabled=true \
--set controller.tolerations[0].key=node-role.kubernetes.io/control-plane \
--set controller.tolerations[0].operator=Exists \
--set controller.tolerations[0].effect=NoSchedule \
--set controller.tolerations[1].key=node-role.kubernetes.io/master \
--set controller.tolerations[1].operator=Exists \
--set controller.tolerations[1].effect=NoSchedule
environment:
KUBECONFIG: /home/vagrant/.kube/config
해결 방법 3: 이미 배포된 리소스에 패치 적용하기 🩹
이미 배포된 리소스에는 다음과 같이 패치를 적용할 수 있습니다:
# Ansible 태스크
- name: Apply tolerations patch to MetalLB components
become: yes
become_user: vagrant
shell: |
kubectl -n metallb-system patch deployment controller --type=json -p='[{"op":"add","path":"/spec/template/spec/tolerations","value":[{"key":"node-role.kubernetes.io/control-plane","operator":"Exists","effect":"NoSchedule"}]}]' || true
environment:
KUBECONFIG: /home/vagrant/.kube/config
문제 2: 자원 부족으로 인한 오류! 📉
파드 스케줄링 문제를 해결했지만, 이번에는 다른 문제가 발생했어요:
Error: UPGRADE FAILED: pre-upgrade hooks failed: 1 error occurred:
timed out waiting for the condition
원인 분석 🔍
이러한 타임아웃 오류는 주로 다음과 같은 이유로 발생합니다:
- 노드의 CPU/메모리 자원 부족
- 이미지 다운로드 지연
- 네트워크 문제
해결 방법: 리소스 제한 설정 및 상태 확인 💡
먼저 클러스터 상태를 확인하는 태스크를 추가했습니다:
- name: Check system resources and cluster status
become: yes
become_user: vagrant
shell: |
echo "=== Node Status ==="
kubectl get nodes
echo "=== System Resources ==="
free -h
df -h
echo "=== Pod Status ==="
kubectl get pods --all-namespaces
environment:
KUBECONFIG: /home/vagrant/.kube/config
register: system_resources
그리고 리소스 제한을 보다 보수적으로 설정했습니다:
- name: Install Nginx Ingress Controller
# ...
shell: |
helm install ingress-nginx ingress-nginx/ingress-nginx \
# ...
--set controller.resources.requests.cpu=100m \
--set controller.resources.requests.memory=128Mi \
--set controller.resources.limits.cpu=200m \
--set controller.resources.limits.memory=256Mi
문제 3: YAML 문법과 백슬래시 문제 😱
Ansible 플레이북에서 가장 골치 아팠던 문제는 백슬래시(\
)로 여러 줄 명령을 작성할 때 발생했어요:
/bin/sh: 2: --set: not found
원인 분석 🔍
이 오류는 YAML 문법에서 멀티라인 문자열과 셸 명령어의 줄 바꿈 처리가 제대로 되지 않을 때 발생합니다. 특히 백슬래시 뒤에 공백이 있거나, 다음 줄의 들여쓰기가 부적절할 때 문제가 생깁니다.
해결 방법 1: 백슬래시 사용법 주의하기 ⚠️
백슬래시를 사용할 때는 다음 규칙을 지켜야 합니다:
- 백슬래시 바로 뒤에 공백이나 주석이 없어야 함
- 다음 줄은 적절히 들여쓰기 되어야 함
# 올바른 방법
- name: Helm command with backslashes
shell: |
helm upgrade nginx nginx/nginx \
--set key1=value1 \
--set key2=value2
해결 방법 2: 적절한 셸 지정하기 🐚
/bin/sh
대신 /bin/bash
를 명시적으로 지정하는 것이 도움이 될 수 있습니다:
- name: Helm command with explicit bash
shell: |
helm upgrade nginx nginx/nginx \
--set key1=value1 \
--set key2=value2
args:
executable: /bin/bash
해결 방법 3: 한 줄로 작성하기 📝
가장 확실한 방법은 모든 명령을 한 줄로 작성하는 것입니다:
- name: Helm command in one line
shell: helm upgrade nginx nginx/nginx --set key1=value1 --set key2=value2
하지만 이 방법은 가독성이 떨어지므로 명령이 짧은 경우에만 권장합니다.
정리: 통합 해결 방법 🎯
앞서 소개한 모든 문제를 해결하는 Ansible 태스크 예시를 소개합니다:
# 1. 테인트 제거
- name: Remove control-plane taint
shell: |
kubectl taint nodes --all node-role.kubernetes.io/control-plane- || true
kubectl taint nodes --all node-role.kubernetes.io/master- || true
args:
executable: /bin/bash
# 2. 리소스 상태 확인
- name: Check system resources
shell: |
echo "=== System Resources ==="
free -h
df -h
echo "=== Pod Status ==="
kubectl get pods --all-namespaces
register: system_resources
# 3. 톨러레이션과 적절한 백슬래시 사용
- name: Install component with tolerations
shell: |
helm install component repo/component \
--namespace example \
--set tolerations[0].key=node-role.kubernetes.io/control-plane \
--set tolerations[0].operator=Exists \
--set tolerations[0].effect=NoSchedule \
--set resources.requests.cpu=100m \
--set resources.requests.memory=128Mi
args:
executable: /bin/bash
결론 🌟
Ansible로 쿠버네티스 환경을 구축할 때는 다음 사항에 주의하세요:
- 단일 노드 클러스터에서는 테인트 제거하기: 마스터 노드도 워크로드를 실행할 수 있게 합니다.
- 리소스 모니터링 습관화하기: 문제 발생 시 먼저 자원 상태를 확인합니다.
- YAML 문법과 백슬래시 주의하기: 백슬래시 사용 시 공백과 들여쓰기에 주의합니다.
- 명시적인 셸 지정하기: 가능하면
args: executable: /bin/bash
를 추가합니다.
이러한 문제들을 미리 알고 대비한다면, Ansible로 쿠버네티스 환경을 더 효율적으로 구축할 수 있을 거예요! 🚀
즐거운 쿠버네티스 여행 되세요! 🌈
#Kubernetes #Ansible #DevOps #Taint #Toleration
Common Issues and Solutions When Setting Up Kubernetes with Ansible 🚀
Hello everyone! Today I’d like to share some of the problems I encountered while setting up a Kubernetes environment using Ansible, and how I solved them. We’ll focus particularly on pod scheduling in single-node clusters, resource management, and YAML syntax issues! 😊
Understanding Taints and Tolerations 🧩
First, let’s understand two important Kubernetes concepts: taints and tolerations.
What are Taints? 🚫
Taints are like “No Entry” signs that you place on nodes to prevent certain pods from being scheduled on them. They’re primarily used to protect specialized nodes (like GPU nodes or master nodes).
# Applying a taint to a node
kubectl taint nodes node1 key=value:NoSchedule
# Checking taints on a node
kubectl describe node node1 | grep Taints
Taints can have three different effects:
- NoSchedule: Pods without matching tolerations will never be scheduled
- PreferNoSchedule: The system will try to avoid scheduling pods without matching tolerations
- NoExecute: Pods without matching tolerations will be evicted if already running
What are Tolerations? 🛡️
Tolerations are like “Entry Permits” that you give to pods, allowing them to be scheduled on nodes with matching taints. If a taint is the “No Entry” sign on a node, a toleration is the special pass that lets a pod ignore that sign.
# Adding tolerations to a pod
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx
tolerations:
- key: "key"
operator: "Equal"
value: "value"
effect: "NoSchedule"
Master Node Taints 🎮
By default, Kubernetes applies the following taints to master nodes (control plane nodes):
node-role.kubernetes.io/control-plane:NoSchedule
node-role.kubernetes.io/master:NoSchedule (older versions)
These taints mean “Don’t schedule regular workloads on the master node!” However, in single-node clusters or development environments, it makes sense to either remove these taints or add tolerations to all pods to make use of the master node!
Problem 1: Pods Stuck in Pending State! 🕐
The first problem I encountered when setting up the Kubernetes environment for the MoolMeow project was that almost all pods were stuck in the Pending state.
$ kubectl get pods --all-namespaces
NAMESPACE NAME READY STATUS RESTARTS AGE
ingress-nginx ingress-nginx-admission-create-qpm8v 0/1 Pending 0 33m
metallb-system controller-5f56cd6f78-fghps 0/1 Pending 0 22h
metallb-system speaker-plpxf 0/1 Pending 0 22h
rook-ceph rook-ceph-operator-6dd77b4466-kv7pq 0/1 Pending 0 22h
Checking the event logs showed this message:
0/1 nodes are available: 1 node(s) had untolerated taint {node-role.kubernetes.io/control-plane: }
Cause Analysis 🔍
This problem occurred because our cluster had a single-node configuration (just the master node), and since master nodes have taints by default, regular workloads weren’t being scheduled on it.
Solution 1: Remove the Taints ✂️
The simplest solution is to remove the taints from the master node:
# Ansible task
- name: Remove control-plane taint to allow pods to be scheduled on master
become: yes
become_user: vagrant
shell: |
kubectl taint nodes --all node-role.kubernetes.io/control-plane- || true
kubectl taint nodes --all node-role.kubernetes.io/master- || true
environment:
KUBECONFIG: /home/vagrant/.kube/config
Note that adding a -
after the taint name means we’re removing that taint!
Solution 2: Add Tolerations 🛠️
Another approach is to add tolerations to all components. If you’re using Helm charts, you can do this:
# Ansible task (Nginx Ingress installation example)
- name: Install Nginx Ingress Controller
become: yes
become_user: vagrant
shell: |
helm install ingress-nginx ingress-nginx/ingress-nginx \
--namespace ingress-nginx \
--create-namespace \
--set controller.service.type=LoadBalancer \
--set controller.publishService.enabled=true \
--set controller.tolerations[0].key=node-role.kubernetes.io/control-plane \
--set controller.tolerations[0].operator=Exists \
--set controller.tolerations[0].effect=NoSchedule \
--set controller.tolerations[1].key=node-role.kubernetes.io/master \
--set controller.tolerations[1].operator=Exists \
--set controller.tolerations[1].effect=NoSchedule
environment:
KUBECONFIG: /home/vagrant/.kube/config
Solution 3: Apply Patches to Already Deployed Resources 🩹
For resources that are already deployed, you can apply patches:
# Ansible task
- name: Apply tolerations patch to MetalLB components
become: yes
become_user: vagrant
shell: |
kubectl -n metallb-system patch deployment controller --type=json -p='[{"op":"add","path":"/spec/template/spec/tolerations","value":[{"key":"node-role.kubernetes.io/control-plane","operator":"Exists","effect":"NoSchedule"}]}]' || true
environment:
KUBECONFIG: /home/vagrant/.kube/config
Problem 2: Resource Shortage Errors! 📉
After solving the pod scheduling problem, a new issue appeared:
Error: UPGRADE FAILED: pre-upgrade hooks failed: 1 error occurred:
timed out waiting for the condition
Cause Analysis 🔍
These timeout errors usually occur for the following reasons:
- Node CPU/memory resource shortage
- Image download delays
- Network issues
Solution: Set Resource Limits and Check Status 💡
I added a task to check the cluster status:
- name: Check system resources and cluster status
become: yes
become_user: vagrant
shell: |
echo "=== Node Status ==="
kubectl get nodes
echo "=== System Resources ==="
free -h
df -h
echo "=== Pod Status ==="
kubectl get pods --all-namespaces
environment:
KUBECONFIG: /home/vagrant/.kube/config
register: system_resources
And set more conservative resource limits:
- name: Install Nginx Ingress Controller
# ...
shell: |
helm install ingress-nginx ingress-nginx/ingress-nginx \
# ...
--set controller.resources.requests.cpu=100m \
--set controller.resources.requests.memory=128Mi \
--set controller.resources.limits.cpu=200m \
--set controller.resources.limits.memory=256Mi
Problem 3: YAML Syntax and Backslash Issues 😱
The most annoying problem when writing Ansible playbooks was dealing with backslashes (\
) for multi-line commands:
/bin/sh: 2: --set: not found
Cause Analysis 🔍
This error occurs when YAML multiline strings and shell command line breaks aren’t properly handled. It’s especially common when there are spaces after backslashes or when the next line’s indentation is improper.
Solution 1: Be Careful with Backslash Usage ⚠️
When using backslashes, follow these rules:
- No spaces or comments immediately after the backslash
- Proper indentation on the next line
# Correct method
- name: Helm command with backslashes
shell: |
helm upgrade nginx nginx/nginx \
--set key1=value1 \
--set key2=value2
Solution 2: Specify the Shell 🐚
Explicitly specifying /bin/bash
instead of the default /bin/sh
can help:
- name: Helm command with explicit bash
shell: |
helm upgrade nginx nginx/nginx \
--set key1=value1 \
--set key2=value2
args:
executable: /bin/bash
Solution 3: Write Commands in a Single Line 📝
The most reliable method is to write all commands in a single line:
- name: Helm command in one line
shell: helm upgrade nginx nginx/nginx --set key1=value1 --set key2=value2
But this approach reduces readability, so it’s only recommended for short commands.
Summary: Integrated Solutions 🎯
Here’s an example of Ansible tasks that address all the issues we’ve discussed:
# 1. Remove taints
- name: Remove control-plane taint
shell: |
kubectl taint nodes --all node-role.kubernetes.io/control-plane- || true
kubectl taint nodes --all node-role.kubernetes.io/master- || true
args:
executable: /bin/bash
# 2. Check resource status
- name: Check system resources
shell: |
echo "=== System Resources ==="
free -h
df -h
echo "=== Pod Status ==="
kubectl get pods --all-namespaces
register: system_resources
# 3. Use tolerations and proper backslashes
- name: Install component with tolerations
shell: |
helm install component repo/component \
--namespace example \
--set tolerations[0].key=node-role.kubernetes.io/control-plane \
--set tolerations[0].operator=Exists \
--set tolerations[0].effect=NoSchedule \
--set resources.requests.cpu=100m \
--set resources.requests.memory=128Mi
args:
executable: /bin/bash
Conclusion 🌟
When building a Kubernetes environment with Ansible, keep these points in mind:
- Remove taints in single-node clusters: Allow the master node to run workloads.
- Make resource monitoring a habit: Check resource status first when problems occur.
- Be careful with YAML syntax and backslashes: Pay attention to spaces and indentation when using backslashes.
- Specify the shell explicitly: When possible, add
args: executable: /bin/bash
.
By being aware of these issues and preparing for them, you can build a Kubernetes environment more efficiently with Ansible! 🚀
Enjoy your Kubernetes journey! 🌈
#Kubernetes #Ansible #DevOps #Taint #Toleration