
생성 수정
Kubernetes · 7장
K3s 사용법
단일 바이너리 배포판 K3s로 클러스터를 세우고, 배포·노출·설정·볼륨·스케일·롤백·정리까지 한 바퀴를 직접 돌아본다. 명령은 Ubuntu 기준이며 그대로 복사해 실행할 수 있다.
7장 · 총 10개 절 · 전체 9개 장 중 7번째
쿠버네티스가 실제로 푸는 문제
7장 · 01
컨테이너 하나를 띄우는 건 docker run 한 줄이면 끝납니다. 문제는 그 다음입니다 — 죽으면 누가 다시 띄우는가, 트래픽이 몰리면 몇 개로 늘리는가, 새 버전으로 바꾸는 동안 서비스는 어떻게 유지하는가. 쿠버네티스는 이 "운영"을 사람 대신 맡는 제어 루프입니다.
- 선언형(declarative) — 원하는 상태를 YAML로 적어두면, 컨트롤러가 현재 상태를 그쪽으로 계속 맞춘다
- 자가 치유 — 파드가 죽거나 노드가 빠지면 스케줄러가 다른 자리에 다시 띄운다
- 수평 확장 — replicas 숫자 하나로 인스턴스를 늘리고 줄인다
- 무중단 배포 — 롤링 업데이트로 새 파드가 준비된 뒤에 옛 파드를 내린다
- 서비스 디스커버리 — 파드 IP가 바뀌어도 서비스 이름(DNS)으로 항상 찾아간다
- 핵심 객체는 넷이다 — 파드(실행 단위), 디플로이먼트(개수·버전 관리), 서비스(고정 접점), 인그레스(외부 진입로)
왜 K3s인가 — 실습에 맞는 배포판
7장 · 02
쿠버네티스를 제대로 설치하려면 etcd·API 서버·컨트롤러 매니저·스케줄러·CNI를 각각 세워야 합니다. K3s는 이 전부를 단일 바이너리(약 70MB) 하나로 묶은 CNCF 인증 배포판입니다. 실습용 축소판이 아니라 API가 같은 정식 쿠버네티스라, 여기서 익힌 명령이 EKS·GKE에서 그대로 통합니다.
- 설치가 한 줄이다 — 스크립트 하나로 컨트롤 플레인·kubelet·CNI·kubectl까지 함께 깔린다
- etcd 대신 SQLite를 기본으로 써서 단일 노드에서도 가볍게 뜬다(다중 서버 구성 시 etcd·외부 DB로 바꿀 수 있다)
- Traefik 인그레스 컨트롤러, ServiceLB 로드밸런서, local-path 스토리지가 처음부터 들어 있다 — 별도 설치 없이 인그레스·볼륨 실습을 바로 한다
- 메모리 512MB급 라즈베리파이·소형 VM에서도 돈다 — 온프레미스 소규모 운영에도 실제로 쓰인다
- kubectl·YAML·개념이 모두 표준이라 학습 결과가 관리형 쿠버네티스로 그대로 옮겨간다
실습 환경 준비물
- Ubuntu 20.04 LTS 이상 — 물리 서버·VM·클라우드 인스턴스 어느 쪽이든 된다(Windows라면 WSL2의 우분투)
- 메모리 1GB 이상, 여유 디스크 2GB 이상 — 워커를 붙일 계획이면 같은 사양의 두 번째 머신
- sudo 권한과 인터넷 연결(설치 스크립트를 내려받는다)
- 노드끼리 6443(API 서버)·10250(kubelet) 포트가 서로 열려 있을 것
먼저 시스템 패키지를 최신으로 맞춘다.
WSL2에서 실습한다면 systemd가 켜져 있어야 K3s 서비스가 뜬다 — /etc/wsl.conf에 아래를 넣고 PowerShell에서 wsl --shutdown 후 다시 들어온다.
[boot]
systemd=true서버 노드 설치와 kubectl 연결
7장 · 03
K3s는 컨트롤 플레인 역할을 하는 노드를 서버(server), 워크로드만 받는 노드를 에이전트(agent)라고 부릅니다. 먼저 서버 하나를 세우면 그 자체로 이미 한 대짜리 클러스터가 완성됩니다.
- 설치 스크립트는 systemd 서비스(k3s)를 등록하고 부팅 시 자동 시작까지 잡아준다
- kubeconfig는 /etc/rancher/k3s/k3s.yaml 에 생성된다 — root 소유라 그대로는 일반 계정에서 못 읽는다
- 그 파일을 ~/.kube/config 로 복사해 소유자를 바꾸면 sudo 없이 kubectl을 쓸 수 있다
- 노드가 Ready 로 바뀌면 컨트롤 플레인이 정상 기동한 것이다 — 보통 30초 안팎 걸린다
1. 서버 노드 설치
공식 설치 스크립트를 그대로 실행한다. 한 줄로 끝난다.
서비스가 떴는지 확인한다.
2. kubectl을 내 계정으로 연결
kubeconfig를 홈 디렉터리로 복사하고 소유자를 바꾼다. 이 뒤로는 sudo 없이 kubectl을 쓴다.
3. 클러스터 확인
노드가 Ready 이고 kube-system 파드들이 Running 이면 설치가 끝난 것이다.
kube-system 에 traefik(인그레스), svclb(로드밸런서), local-path-provisioner(스토리지), coredns(DNS)가 이미 떠 있다 — 뒤 단계에서 이것들을 그대로 쓴다.
에이전트 노드 붙여 다중 노드로 확장
7장 · 04
두 번째 머신이 있다면 에이전트로 붙여 실제 다중 노드 클러스터를 만듭니다. 서버가 발급한 토큰과 서버 주소 두 가지만 있으면 됩니다. 머신이 하나뿐이면 이 단계는 건너뛰어도 이후 실습에 지장이 없습니다.
- 노드 토큰은 서버 노드의 /var/lib/rancher/k3s/server/node-token 에 있다 — 사실상 클러스터 가입 자격증명이므로 외부에 노출하지 않는다
- 에이전트는 K3S_URL 이 지정되면 자동으로 agent 모드로 설치된다(그 환경변수가 없으면 또 하나의 서버가 된다)
- 가입이 끝나면 서버 노드에서 kubectl get nodes 에 두 대가 보인다
- ROLES 열이 control-plane,master 면 서버, <none> 이면 에이전트다
- kubectl 은 서버 노드에서만 쓴다 — 에이전트에는 kubeconfig가 없다
1. 서버에서 토큰과 IP 확인
2. 에이전트 머신에서 가입
<서버IP>와 <토큰>을 위에서 확인한 값으로 바꿔 실행한다.
3. 서버에서 합류 확인
첫 배포 — 파드와 디플로이먼트
7장 · 05
파드는 컨테이너를 감싼 최소 실행 단위이고, 디플로이먼트는 "이 파드를 몇 개 유지하라"는 선언입니다. 실무에서 파드를 직접 만드는 일은 거의 없습니다 — 디플로이먼트를 만들고 파드는 그 결과로 생기게 둡니다.
- kubectl create deployment 는 명령형(imperative), YAML + apply 는 선언형(declarative) 방식이다 — 실습은 명령형으로 시작하고 실제 운영은 YAML을 파일로 관리한다
- 파드를 손으로 지워봐도 디플로이먼트가 즉시 새 파드를 다시 만든다 — 자가 치유를 눈으로 확인하는 지점이다
- --dry-run=client -o yaml 을 붙이면 명령형 실행 없이 YAML 초안을 뽑을 수 있다
- kubectl describe 는 이벤트까지 보여주므로 파드가 Pending·CrashLoopBackOff 일 때 첫 번째로 볼 곳이다
1. 명령형으로 빠르게 띄우기
2. 자가 치유 확인
파드 하나를 지워보고, 곧바로 새 파드가 생기는지 본다.
3. 선언형으로 다시 쓰기
같은 배포를 YAML로 적어두면 형상 관리가 가능해진다. 파일로 저장한 뒤 apply 한다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 2
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.27
ports:
- containerPort: 80
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi서비스와 인그레스로 외부에 노출
7장 · 06
파드 IP는 재시작마다 바뀝니다. 서비스는 그 앞에 고정 진입점을 두고, 인그레스는 여러 서비스를 도메인·경로 기준으로 갈라 하나의 80/443 포트 뒤에 모읍니다. K3s에는 Traefik 인그레스 컨트롤러가 이미 들어 있어 별도 설치가 필요 없습니다.
- ClusterIP(기본) — 클러스터 안에서만 접근, 서비스 이름이 곧 DNS 이름이 된다
- NodePort — 모든 노드의 30000~32767 포트 하나를 열어 외부에서 접근한다(실습에서 가장 빠른 확인 방법)
- LoadBalancer — K3s의 ServiceLB(svclb)가 노드 포트를 80 같은 표준 포트에 매핑해준다
- 인그레스 — 호스트·경로 규칙으로 여러 서비스를 한 진입점 뒤에 정리한다. HTTPS 종료도 여기서 한다
- kubectl port-forward 는 노출이 아니라 디버깅용 터널이다 — 명령을 끝내면 사라진다
1. 서비스로 고정 접점 만들기
클러스터 안에서 서비스 이름으로 접근되는지 임시 파드로 확인한다.
2. 인그레스로 도메인·경로 라우팅
Traefik이 이미 떠 있으므로 인그레스 객체만 만들면 된다. sslip.io 같은 와일드카드 DNS를 쓰면 도메인 없이도 호스트 규칙을 시험해볼 수 있다(<노드IP>를 실제 IP로 바꾼다).
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web
spec:
ingressClassName: traefik
rules:
- host: web.<노드IP>.sslip.io
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-svc
port:
number: 803. 급할 때는 포트포워딩
노출 설정을 건드리지 않고 로컬에서만 잠깐 열어보는 방법이다.
컨피그맵과 시크릿 — 설정을 이미지 밖으로
7장 · 07
설정값을 이미지 안에 굽으면 환경이 바뀔 때마다 이미지를 다시 만들어야 합니다. 쿠버네티스는 설정을 컨피그맵, 민감한 값을 시크릿으로 분리해 파드에 환경변수나 파일로 주입합니다.
- 컨피그맵 — 일반 설정값(로그 레벨, 외부 API 주소 등). 평문으로 저장된다
- 시크릿 — 비밀번호·토큰·인증서. base64로 인코딩될 뿐 암호화가 아니므로 RBAC과 저장소 암호화가 함께 필요하다
- 주입 방식은 두 가지다 — envFrom 으로 환경변수, volumeMounts 로 파일
- 컨피그맵을 바꿔도 이미 뜬 파드에는 환경변수가 반영되지 않는다 — kubectl rollout restart 로 다시 띄운다
- YAML에 비밀값을 적어 저장소에 커밋하지 않는다 — 명령형으로 만들거나 외부 시크릿 관리자를 붙인다
1. 만들기
2. 파드에 주입
디플로이먼트의 컨테이너 정의에 envFrom 을 추가한다.
containers:
- name: nginx
image: nginx:1.27
envFrom:
- configMapRef:
name: web-config
- secretRef:
name: web-secret3. 값이 바뀌었을 때
설정을 갱신한 뒤에는 파드를 다시 띄워야 새 값이 들어간다.
볼륨 — 파드가 죽어도 남는 데이터
7장 · 08
파드의 파일시스템은 파드와 함께 사라집니다. 데이터를 남기려면 볼륨을 붙여야 하고, 쿠버네티스는 이를 PVC(요청)와 PV(실제 저장 공간)로 나눠 다룹니다. K3s는 local-path 프로비저너를 기본 제공해 PVC를 만들면 노드 디스크에 자동으로 공간을 잡아줍니다.
- PVC(PersistentVolumeClaim) — "이만큼의 저장 공간이 필요하다"는 요청서
- PV(PersistentVolume) — 실제 저장 공간. K3s의 local-path 스토리지클래스가 PVC를 보고 자동 생성한다
- local-path 는 특정 노드의 디스크에 묶인다 — 파드가 다른 노드로 옮겨가면 데이터를 따라가지 못한다(다중 노드 운영에서는 NFS·Longhorn 같은 공유 스토리지를 쓴다)
- 상태를 가진 워크로드(DB 등)는 디플로이먼트가 아니라 스테이트풀셋으로 다루는 편이 맞다
- 실습이 끝나면 PVC를 지워야 디스크가 회수된다 — 파드를 지워도 PVC는 남는다
1. 저장 공간 요청하기
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: web-data
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-path
resources:
requests:
storage: 1Gi2. 파드에 마운트
containers:
- name: nginx
image: nginx:1.27
volumeMounts:
- name: data
mountPath: /usr/share/nginx/html
volumes:
- name: data
persistentVolumeClaim:
claimName: web-data파드를 지웠는데도 마지막 명령이 hello k3s 를 그대로 출력하면, 데이터가 파드 바깥에 남았다는 뜻이다.
스케일과 롤링 업데이트
7장 · 09
개수를 늘리는 일과 버전을 바꾸는 일 모두 디플로이먼트가 맡습니다. 롤링 업데이트는 새 파드가 준비 상태가 된 뒤에야 옛 파드를 내리기 때문에, 준비성 프로브(readinessProbe)를 제대로 걸어야 무중단이 실제로 성립합니다.
- kubectl scale 로 즉시 늘리고 줄인다 — 실제 운영에서는 HPA(오토스케일러)로 부하에 맞춰 자동화한다
- 이미지 태그를 바꾸면 롤아웃이 시작되고, rollout status 로 진행을 지켜본다
- readinessProbe — 트래픽을 받을 준비가 됐는지. 실패하면 서비스에서 빠진다
- livenessProbe — 살아 있는지. 실패하면 컨테이너를 재시작한다
- 잘못 나갔으면 rollout undo 로 직전 리비전으로 되돌린다 — 배포 이력이 남아 있다
- latest 태그는 쓰지 않는다 — 무엇이 배포됐는지 특정할 수 없고 되돌릴 기준도 사라진다
1. 개수 조절
2. 프로브 걸기
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 3
periodSeconds: 5
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 10
periodSeconds: 103. 버전 올리고 되돌리기
문제 추적과 클러스터 정리
7장 · 10
실습에서 막히는 지점은 대부분 정해져 있습니다 — 파드가 Pending 이거나, CrashLoopBackOff 이거나, 서비스에 연결이 안 되는 경우입니다. 확인 순서를 정해두면 원인을 빠르게 좁힐 수 있고, 마지막에는 클러스터를 깨끗이 지우는 방법까지 알아둬야 합니다.
- Pending — 스케줄할 노드가 없다. describe 의 Events 에서 자원 부족·테인트 여부를 본다
- ImagePullBackOff — 이미지 이름·태그 오타이거나 프라이빗 레지스트리 인증이 없다
- CrashLoopBackOff — 컨테이너가 계속 죽는다. logs --previous 로 죽기 직전 로그를 본다
- 서비스 연결 실패 — 서비스의 selector 와 파드 label 이 어긋난 경우가 가장 흔하다. get endpoints 가 비어 있으면 이 문제다
- K3s 자체 문제는 journalctl -u k3s 로 서비스 로그를 본다
- 제거 스크립트는 설치 시 함께 깔린다 — 서버는 k3s-uninstall.sh, 에이전트는 k3s-agent-uninstall.sh 다
1. 확인 순서
2. 노드·자원 상태
kubectl top 은 metrics-server 가 떠 있어야 동작한다 — K3s에는 기본 포함되어 있고, 갓 설치한 직후에는 수집 전이라 잠시 오류가 날 수 있다.
3. 실습 리소스 정리
만든 객체만 지우면 클러스터는 그대로 남는다.
4. K3s 자체 제거
클러스터를 통째로 걷어낼 때만 실행한다 — 컨테이너·데이터·설정이 모두 사라진다.
다음으로 볼 것
- 헬름(Helm) — 매니페스트 묶음을 차트로 패키징하고 값만 바꿔 배포한다
- 네임스페이스와 RBAC — 환경을 나누고 접근 권한을 제한한다
- 스테이트풀셋과 공유 스토리지 — DB처럼 상태를 가진 워크로드를 다룬다
- GitOps(Argo CD·Flux) — 저장소의 YAML을 클러스터의 단일 진실 공급원으로 삼는다
- K3s 다중 서버(HA) 구성 — etcd 또는 외부 DB로 컨트롤 플레인을 이중화한다
LangTailor