Kubernetes Study · 02 전반 ②
쿠버네티스 전반 ② — 보안 · 스토리지 · 네트워크
전반 세 노트 중 두 번째예요. 7~11부에서 누가 무엇을 할 수 있는지(인증 · 인가 · 보안), 데이터를 어디에 남기는지(스토리지), 패킷이 어떻게 오가는지(네트워크 기초 · 쿠버네티스 네트워킹)를 봐요. 앞 내용은 ① 클러스터 운영, 다음은 ③ 설치 · 도구 · 트러블슈팅이에요.
01. 보안의 큰 그림
1부 04에서 API 서버는 요청마다 인증 → 인가 → 어드미션 관문을 통과시킨다고 했어요. 7부는 그 관문 하나하나와, 관문이 믿고 쓰는 인증서(TLS)를 다뤄요. 8부에서는 그 밖의 보안(이미지 · Pod 권한 · 네트워크)을 봐요.
| 질문 | 쿠버네티스의 답 | 절 |
|---|---|---|
| 누가 API 서버에 들어오나? (인증) | 인증서 · 토큰 · OIDC 같은 외부 로그인 | 02 · 04 · 05 |
| 들어온 사람이 무엇을 할 수 있나? (인가) | RBAC (Role · RoleBinding) | 06 · 07 |
| Pod는 어떻게 API를 부르나? | ServiceAccount 토큰 | 08 |
| 요청 내용이 규칙에 맞나? (어드미션) | Admission Controller · 웹훅 · 정책 | 09 |
| 부품 사이 통신은 안전한가? | TLS 인증서 (모든 부품이 서로 확인) | 03 · 04 |
| Pod끼리, 컨테이너 자체는? | Network Policy · SecurityContext · 이미지 보안 | 8부 |
02. 인증
1. 사람 사용자와 ServiceAccount
API 서버에 접근하는 쪽은 크게 둘이에요. 관리자 · 개발자 같은 사람, 그리고 Pod 안의 앱이나 CI 같은 기계. 놀랍게도 쿠버네티스에는 “User” 객체가 없어요. 사람은 클러스터 밖에서 관리하고(인증서 · 회사 로그인), 기계용 계정만 ServiceAccount라는 객체로 클러스터 안에서 만들어요.
$ kubectl create serviceaccount ci-bot # 기계 계정은 객체로 만든다
$ kubectl create user jane # 이런 명령은 없다 — 사람은 객체가 아님
error: unknown command "user" for "kubectl create"
2. 인증 방법들
| 방법 | 어떻게 | 지금은 |
|---|---|---|
| X.509 클라이언트 인증서 | 인증서의 CN = 사용자 이름, O = 그룹 | kubeadm 관리자 계정 · 컴포넌트끼리 (04) |
| ServiceAccount 토큰 | API 서버가 서명한 시간 제한 토큰 | Pod · CI (08) |
| OIDC | 회사 로그인(Google · Okta · Keycloak 등)이 발급한 토큰 | 운영 환경 사람 계정의 표준 |
| 웹훅 · 인증 프록시 | 외부 서비스에 “이 토큰 누구야?”를 물어봄 | 클라우드(GKE · EKS)의 IAM 연동 등 |
정적 토큰 파일 (--token-auth-file) | CSV에 토큰 · 이름 나열 | 바꾸려면 API 서버 재시작 — 실습용 |
| 정적 비밀번호 파일 (basic auth) | CSV에 비밀번호 | 1.19에서 제거 — 학습 자료에 나오면 옛 방식으로 이해 |
03. TLS 기초
쿠버네티스의 거의 모든 통신은 TLS로 암호화되고, 서로를 인증서로 확인해요. 인증서가 무엇인지부터 잡고 가요. 학습 자료도 이 부분을 꽤 길게 다뤄요.
1. 대칭키와 비대칭키
| 대칭키 | 비대칭키 (공개키 · 개인키) | |
|---|---|---|
| 키 | 잠그는 키 = 여는 키 | 공개키(자물쇠)로 잠그면 개인키로만 열림 |
| 장점 | 빠름 | 공개키는 누구에게 줘도 됨 |
| 약점 | 키를 상대에게 안전하게 건네기 어려움 | 느림 |
| TLS에서 | 실제 데이터 암호화 | 처음에 대칭키를 안전하게 주고받을 때 |
2. 인증서와 CA
인증서는 “이 공개키의 주인은 api.example.com이다”라는 문서에 CA(인증 기관)가 서명한 거예요. 받는 쪽은 미리 가지고 있는 CA의 공개키로 서명을 확인해서 가짜 서버를 걸러요. 브라우저는 세계적인 CA 목록을 내장하고 있고, 쿠버네티스는 클러스터 전용 CA를 하나 만들어 모든 부품이 그 CA를 믿게 해요.
| 파일 | 무엇 | 보통 이름 |
|---|---|---|
| 개인키 | 절대 밖으로 안 나감 | *.key · *-key.pem |
| 인증서 (공개키 + 서명) | 상대에게 보여 주는 신분증 | *.crt · *.pem |
| CSR (서명 요청) | “이 공개키에 서명해 주세요” | *.csr |
04. 쿠버네티스의 TLS
1. 컴포넌트별 인증서 지도
인증서 (/etc/kubernetes/pki/) | 종류 | 쓰는 곳 |
|---|---|---|
ca.crt · ca.key | CA | 클러스터의 모든 인증서에 서명 (기본 10년) |
apiserver.crt | 서버 | API 서버가 자신을 증명 |
apiserver-etcd-client.crt | 클라이언트 | API 서버 → etcd |
apiserver-kubelet-client.crt | 클라이언트 | API 서버 → kubelet (logs · exec) |
etcd/ca.crt · etcd/server.crt · etcd/peer.crt | etcd 전용 CA · 서버 · 복제 | etcd (1부 03의 etcdctl 인증서) |
sa.key · sa.pub | 서명 키 쌍 | ServiceAccount 토큰 서명 (08) |
/etc/kubernetes/admin.conf 등 *.conf | 클라이언트 인증서를 품은 kubeconfig | 관리자 · 스케줄러 · 컨트롤러 → API 서버 |
2. 인증서 내용 보기 (openssl)
인증서 문제는 대부분 이름이 안 맞거나, 만료됐거나, 다른 CA가 서명했거나 셋 중 하나예요. openssl로 직접 열어서 확인해요.
$ openssl x509 -in /etc/kubernetes/pki/apiserver.crt -text -noout
Issuer: CN = kubernetes ← 서명한 CA
Validity
Not After : Nov 2 09:12:41 2027 GMT ← 만료일
Subject: CN = kube-apiserver ← 주인
X509v3 Subject Alternative Name: ← 이 이름들로 접속해도 인정
DNS:kubernetes, DNS:kubernetes.default, DNS:kubernetes.default.svc,
DNS:kubernetes.default.svc.cluster.local, DNS:controlplane,
IP Address:10.96.0.1, IP Address:192.168.0.10
$ sudo kubeadm certs check-expiration # 전체 만료일 한 번에
$ sudo kubeadm certs renew all # 갱신 (이후 컨트롤 플레인 재시작)
kubectl이 x509: certificate has expired로 안 돼요. 다행히 6부의 kubeadm upgrade가 갱신도 같이 해 줘서, 1년에 한 번 이상 업그레이드하면 자연스럽게 해결돼요. 안 되면 kubeadm certs renew.
admin.conf는 system:masters 그룹(어떤 권한 설정으로도 막을 수 없는 그룹)에 속했어요. 1.29부터는 일반 그룹 kubeadm:cluster-admins로 바뀌었고, system:masters용 비상 열쇠는 super-admin.conf로 따로 분리됐어요.
3. Certificates API로 인증서 발급
새 팀원 jane에게 클러스터 접근 권한을 줄 때, CA 개인키로 직접 서명하는 대신 CertificateSigningRequest 객체를 만들어 쿠버네티스에 서명을 맡겨요. 서명은 컨트롤러 매니저가 CA 키로 해요.
$ openssl genrsa -out jane.key 2048
$ openssl req -new -key jane.key -subj "/CN=jane/O=dev-team" -out jane.csr # CN=이름, O=그룹
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
name: jane
spec:
request: <cat jane.csr | base64 | tr -d "\n" 의 결과>
signerName: kubernetes.io/kube-apiserver-client
usages: ["client auth"]
expirationSeconds: 86400 # 하루짜리
$ kubectl get csr
NAME SIGNERNAME REQUESTOR CONDITION
jane kubernetes.io/kube-apiserver-client kubernetes-admin Pending
$ kubectl certificate approve jane # (거절은 deny)
$ kubectl get csr jane -o jsonpath='{.status.certificate}' | base64 -d > jane.crt
05. kubeconfig
매번 kubectl --server … --client-certificate … --client-key … --certificate-authority …를 칠 수는 없죠. 이걸 파일에 모아 둔 게 kubeconfig(기본 ~/.kube/config)예요. 입문 2부 04에서 GKE 접속 정보가 저장되던 그 파일이에요.
apiVersion: v1
kind: Config
current-context: admin@prod
clusters:
- name: prod
cluster:
server: https://192.168.0.10:6443
certificate-authority-data: LS0tLS1CRUdJTi... # ca.crt를 base64로 (또는 certificate-authority: 파일 경로)
users:
- name: admin
user:
client-certificate-data: LS0tLS1... # 내 인증서
client-key-data: LS0tLS1... # 내 개인키
- name: jane
user:
client-certificate: /home/jane/jane.crt
client-key: /home/jane/jane.key
contexts:
- name: admin@prod
context: { cluster: prod, user: admin }
- name: jane@prod
context: { cluster: prod, user: jane, namespace: dev }
$ kubectl config view # 비밀값은 가려서 보여 줌
$ kubectl config get-contexts
$ kubectl config use-context jane@prod
$ kubectl config set-context --current --namespace=dev
$ kubectl get pods --kubeconfig=/root/my-kube-config # 다른 파일 쓰기 (또는 KUBECONFIG 환경 변수)
client-key-data에는 개인키가 들어 있어서, 파일만 있으면 누구든 그 사용자로 접속할 수 있어요. 메신저로 보내거나 Git에 올리지 않아요.
06. API Groups
권한(07)을 정확히 주려면 API가 어떻게 생겼는지 알아야 해요. 2부 01에서 본 apiVersion의 그룹이 실제 주소에 그대로 들어가요.
1. core 그룹과 named 그룹
$ kubectl proxy & # 내 kubeconfig 인증으로 localhost:8001에 길을 열어 줌
Starting to serve on 127.0.0.1:8001
$ curl http://localhost:8001/apis/apps/v1 | head
$ curl http://localhost:8001/api/v1/namespaces/default/pods
2. 리소스와 동사(verbs)
| 동사 | kubectl로는 |
|---|---|
get · list · watch | get pod web · get pods · get pods -w |
create | create · apply(없을 때) · run |
update · patch | edit · apply(있을 때) · scale · set image |
delete · deletecollection | delete pod web · delete pods --all |
$ kubectl api-resources -o wide | grep -E "^NAME|^pods |^deployments "
NAME SHORTNAMES APIVERSION NAMESPACED KIND VERBS
pods po v1 true Pod create,delete,deletecollection,get,list,patch,update,watch
deployments deploy apps/v1 true Deployment create,delete,deletecollection,get,list,patch,update,watch
pods/log · pods/exec처럼 하위 리소스도 있어요. “로그는 보되 exec는 못 하게” 같은 권한을 따로 줄 수 있어요.
07. RBAC
인가 방식은 여럿(Node · RBAC · ABAC · Webhook)이지만 표준은 RBAC(역할 기반 접근 제어)예요. “무엇을 할 수 있는가”를 Role로 만들고, “누구에게”를 RoleBinding으로 연결해요. kube-apiserver의 --authorization-mode=Node,RBAC(1부 04)가 이 설정이에요.
1. Role과 RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-reader
namespace: dev
rules:
- apiGroups: [""] # "" = core 그룹 (06)
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "create", "update"]
resourceNames: [] # 특정 이름만 허용할 때
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: jane-pod-reader
namespace: dev
subjects:
- kind: User # User · Group · ServiceAccount
name: jane # 인증서의 CN (04)
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
$ kubectl create role pod-reader -n dev --verb=get,list,watch --resource=pods,pods/log
$ kubectl create rolebinding jane-pod-reader -n dev --role=pod-reader --user=jane
2. ClusterRole과 ClusterRoleBinding
| 조합 | 효과 | 예 |
|---|---|---|
| Role + RoleBinding | 그 Namespace 안에서만 | jane은 dev의 Pod만 |
| ClusterRole + ClusterRoleBinding | 클러스터 전체 (모든 Namespace + 노드 · PV 같은 비Namespace 리소스) | 노드 관리자 · 모니터링 도구 |
| ClusterRole + RoleBinding | ClusterRole의 규칙을 한 Namespace에서만 | 기본 view · edit을 팀 Namespace에만 — 같은 Role을 Namespace마다 만들 필요가 없음 |
쿠버네티스에는 기본 ClusterRole이 있어요. cluster-admin(전부), admin(Namespace 관리), edit(읽기 · 쓰기, 권한 설정 제외), view(읽기 전용, Secret 제외). 새로 만들기 전에 이걸 먼저 써 보세요.
$ kubectl create clusterrole node-reader --verb=get,list,watch --resource=nodes
$ kubectl create clusterrolebinding jane-node-reader --clusterrole=node-reader --user=jane
$ kubectl create rolebinding jane-view -n prod --clusterrole=view --user=jane # prod는 읽기만
3. kubectl auth can-i로 권한 확인
$ kubectl auth can-i create deployments -n dev --as jane
yes
$ kubectl auth can-i delete pods -n prod --as jane
no
$ kubectl auth can-i list pods -n dev --as system:serviceaccount:dev:ci-bot # ServiceAccount로
$ kubectl auth can-i --list -n dev --as jane # 할 수 있는 것 전부
$ kubectl get rolebindings,clusterrolebindings -A -o wide | grep jane # 누가 jane에게 줬나
*(모든 리소스 · 동사)는 정말 필요할 때만 써요.
08. ServiceAccount
1. Pod가 API를 부르는 방법
모니터링 도구 · CI · 오퍼레이터(15부)처럼 Pod 안의 앱이 API를 불러야 할 때가 있어요. Pod는 ServiceAccount의 이름으로 인증하고, 그 ServiceAccount에 RoleBinding으로 권한을 줘요. Namespace마다 default ServiceAccount가 자동으로 생기고, 따로 지정하지 않은 Pod는 이걸 써요(1부 05의 ServiceAccount 컨트롤러).
apiVersion: v1
kind: ServiceAccount
metadata:
name: ci-bot
namespace: dev
automountServiceAccountToken: true # API를 안 부르는 Pod라면 false 권장
---
spec: # Pod
serviceAccountName: ci-bot # 생략하면 default
containers:
- name: app
image: ci:1.0
$ kubectl exec -it web -- ls /var/run/secrets/kubernetes.io/serviceaccount
ca.crt namespace token
$ kubectl create rolebinding ci-bot-edit -n dev --clusterrole=edit --serviceaccount=dev:ci-bot
2. 바뀐 토큰 방식 (시간 제한 토큰)
학습 자료에 나오는 예전 방식은 ServiceAccount를 만들면 만료 없는 토큰이 담긴 Secret이 자동으로 생겼어요. 이 토큰은 유출되면 영원히 쓸 수 있는 위험이 있어서, 1.24부터 자동 생성이 사라졌어요. 지금은 kubelet이 TokenRequest API로 시간 제한 · 대상(audience) 제한 · Pod에 묶인 토큰을 받아 넣고, 만료 전에 계속 새로 바꿔요.
| 예전 (Secret 토큰) | 지금 (projected 토큰) | |
|---|---|---|
| 만료 | 없음 | 있음 (Pod용은 kubelet이 자동 갱신) |
| Pod가 지워지면 | 토큰은 그대로 유효 | 그 Pod에 묶인 토큰은 무효 |
| 생성 | ServiceAccount마다 Secret 자동 생성 | 필요할 때 발급 · Secret 없음 |
| 밖에서 쓸 토큰 | Secret에서 꺼냄 | kubectl create token ci-bot --duration=1h |
type: kubernetes.io/service-account-token인 Secret을 직접 만들고 어노테이션으로 ServiceAccount를 지정하면 예전 방식의 토큰이 생겨요. 다만 쓰지 않는 게 권장이고, 쓰면 주기적으로 교체해요.
09. Admission Controller
인증 · 인가는 “누가, 무엇을 해도 되는가”만 봐요. 하지만 “Pod를 만들 권한은 있는데, latest 태그 이미지는 안 돼” 같은 요청 내용에 대한 규칙은 인가로는 못 걸러요. 그걸 하는 게 어드미션 컨트롤러예요. 학습 자료에서는 스케줄링 주제에 있지만, API 서버 관문의 마지막 단계라 이 부에서 다뤄요.
1. 인가 다음 관문
$ kube-apiserver -h | grep enable-admission-plugins # 기본으로 켜진 목록
$ grep admission /etc/kubernetes/manifests/kube-apiserver.yaml
- --enable-admission-plugins=NodeRestriction
$ kubectl run nginx --image=nginx -n blue
Error from server (NotFound): namespaces "blue" not found ← NamespaceLifecycle이 거절
| 기본으로 켜진 플러그인 (일부) | 종류 | 하는 일 |
|---|---|---|
NamespaceLifecycle | 검사 | 없는 · 지워지는 Namespace에 생성 거절 |
ServiceAccount | 고치기 | Pod에 ServiceAccount · 토큰 볼륨 자동 추가 (08) |
DefaultTolerationSeconds | 고치기 | not-ready · unreachable 300초 toleration 추가 (1부 05) |
LimitRanger · ResourceQuota | 고치기 · 검사 | 기본 requests 채우기 · 쿼터 초과 거절 (3부 06 · 2부 04) |
DefaultStorageClass | 고치기 | StorageClass 없는 PVC에 기본값 (9부) |
PodSecurity | 검사 | Pod Security Standards 위반 거절 (8부 02) |
MutatingAdmissionWebhook · ValidatingAdmissionWebhook | 웹훅 | 외부 서버에 판단을 맡김 (아래 2) |
학습 자료의 NamespaceAutoProvision(없는 Namespace를 자동으로 만들기)처럼 기본으로 꺼진 플러그인은 --enable-admission-plugins에 추가해서 켜고, 끄려면 --disable-admission-plugins를 써요.
2. Validating · Mutating 웹훅
내장 플러그인으로 부족하면 웹훅으로 직접 만든 서버에 판단을 맡겨요. API 서버가 요청 내용을 AdmissionReview로 보내면, 서버가 “허용 / 거절(이유)”, Mutating이라면 “이렇게 고쳐라(JSON 패치)”를 돌려줘요. 서비스 메시의 사이드카 자동 주입, OPA Gatekeeper · Kyverno 같은 정책 도구가 이 방식이에요.
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: image-policy
webhooks:
- name: image-policy.example.com
clientConfig:
service: { namespace: policy, name: image-checker, path: /validate }
caBundle: LS0tLS1... # 웹훅 서버 인증서를 믿기 위한 CA
rules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE"]
resources: ["pods"]
admissionReviewVersions: ["v1"]
sideEffects: None
failurePolicy: Fail # 웹훅 서버가 죽으면 거절 (Ignore면 통과)
3. ValidatingAdmissionPolicy
웹훅은 서버를 따로 띄우고 인증서까지 관리해야 해서 번거롭고, 그 서버가 죽으면 클러스터 생성 요청이 막혀요. ValidatingAdmissionPolicy(1.30 GA)는 규칙을 CEL이라는 짧은 식으로 써서 API 서버 안에서 바로 검사해요. 간단한 검사는 이제 이쪽이 권장이에요.
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: max-replicas
spec:
matchConstraints:
resourceRules:
- apiGroups: ["apps"]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["deployments"]
validations:
- expression: "object.spec.replicas <= 5"
message: "레플리카는 5개까지만 허용"
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: max-replicas-dev
spec:
policyName: max-replicas
validationActions: ["Deny"]
matchResources:
namespaceSelector:
matchLabels: { env: dev }
4. 7부 셀프 체크
쿠버네티스에서 사람 사용자는 어떻게 만드나요?
객체로 만들지 않아요. 클러스터 밖에서 인증서 · OIDC 등으로 신원을 증명하면 API 서버가 이름과 그룹을 알아내요. 기계 계정만 ServiceAccount 객체로 만들어요.TLS에서 비대칭키는 무엇에 쓰고, 실제 데이터는 무엇으로 암호화하나요?
비대칭키는 처음에 대칭키를 안전하게 주고받는 데 쓰고, 실제 데이터는 빠른 대칭키로 암호화해요.인증서가 필요한 이유는? 공개키만 있으면 안 되나요?
가짜 서버가 자기 공개키를 보낼 수 있어서예요. CA가 “이 공개키의 주인은 누구”라고 서명한 인증서로 진짜인지 확인해요.X.509 클라이언트 인증서에서 사용자 이름과 그룹은 어디에 들어가나요?
CN = 사용자 이름, O = 그룹. 예:/CN=jane/O=dev-team.API 서버 인증서의 만료일과 허용된 이름(SAN)을 확인하는 명령은?
openssl x509 -in /etc/kubernetes/pki/apiserver.crt -text -noout. 전체 만료일은kubeadm certs check-expiration.새 사용자 인증서를 쿠버네티스로 서명받는 순서는?
개인키 · CSR 생성 → CertificateSigningRequest 객체 생성 →kubectl certificate approve→.status.certificate를 base64 디코딩해 인증서로 저장.kubeconfig의 세 부분과 context의 역할은?
clusters · users · contexts. context는 “어떤 user로 어떤 cluster에(기본 Namespace까지)”를 묶은 이름이에요.Deployment 권한을 줄 때 Role의 apiGroups에 무엇을 쓰나요?
"apps". Pod · Service 같은 core 그룹은"".ClusterRole을 RoleBinding으로 묶으면?
ClusterRole의 규칙이 그 RoleBinding의 Namespace 안에서만 적용돼요.jane이 prod에서 Pod를 지울 수 있는지 확인하는 명령은?
kubectl auth can-i delete pods -n prod --as jane.1.24 이후 ServiceAccount 토큰은 무엇이 달라졌나요?
Secret이 자동으로 생기지 않고, kubelet이 시간 제한 · Pod에 묶인 토큰을 받아 넣고 자동 갱신해요. 밖에서 쓸 토큰은kubectl create token.Mutating과 Validating 어드미션의 순서와 차이는?
Mutating(요청을 고침)이 먼저, 스키마 검사 뒤 Validating(허용 / 거절만)이 나중이에요.웹훅 서버 없이 “replicas는 5 이하” 규칙을 거는 방법은?
ValidatingAdmissionPolicy에 CEL 식object.spec.replicas <= 5를 쓰고 Binding으로 적용 범위를 정해요.
01. 이미지 보안
7부가 “누가 API에 들어오나”였다면, 8부는 이미 실행된 것을 지키는 방법이에요. 무엇을 실행하나(이미지), 어떤 권한으로 실행하나(SecurityContext), 누구와 통신하나(Network Policy). 먼저 이미지예요.
이미지 이름은 레지스트리/저장소/이름:태그예요(입문 1부 04). 레지스트리를 생략하면 Docker Hub(docker.io), 저장소를 생략하면 library예요. 회사 이미지는 보통 비공개 레지스트리에 두고, 받으려면 로그인 정보가 필요해요.
$ kubectl create secret docker-registry regcred \
--docker-server=myregistry.example.com \
--docker-username=ci-bot --docker-password='…' --docker-email=ci@example.com
spec:
containers:
- name: web
image: myregistry.example.com/team/web:1.4.2
imagePullSecrets:
- name: regcred
$ kubectl patch serviceaccount default -n dev \
-p '{"imagePullSecrets": [{"name": "regcred"}]}' # 이 SA를 쓰는 Pod 전부에 자동으로
| 지킬 것 | 방법 |
|---|---|
| 정확히 그 이미지인가 | 태그 대신 다이제스트로 고정 web@sha256:9b1e… — 태그는 덮어쓸 수 있어요 |
| 매번 새로 받을까 | imagePullPolicy: IfNotPresent(기본) · Always(태그가 latest면 기본) · Never |
| 취약점이 없나 | 빌드할 때 스캐너(Trivy 등)로 검사 |
| 우리가 만든 이미지인가 | 서명(cosign)하고 어드미션(7부 09)에서 서명 없는 이미지 거절 |
| 허용한 레지스트리만 | ValidatingAdmissionPolicy로 image.startsWith('myregistry.example.com/') 같은 규칙 |
02. Pod 보안 설정
컨테이너는 기본적으로 root(UID 0)로 실행되는 경우가 많아요. 컨테이너도 결국 노드의 프로세스라서(입문 1부 03), 격리를 뚫고 나오면 노드의 root가 될 수 있어요. 그래서 “필요한 최소한의 권한으로만” 실행하게 설정해요.
1. SecurityContext
spec:
securityContext: # Pod 전체 (모든 컨테이너 기본값)
runAsUser: 1000
runAsGroup: 3000
fsGroup: 2000 # 볼륨 파일의 그룹
runAsNonRoot: true # root면 시작 거부
seccompProfile:
type: RuntimeDefault # 위험한 시스템 호출 차단
containers:
- name: web
image: web:1.0
securityContext: # 컨테이너별 (Pod 설정보다 우선)
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities: # ← 컨테이너 수준에만 있음
drop: ["ALL"]
add: ["NET_BIND_SERVICE"] # 1024 미만 포트 열기만 허용
volumeMounts:
- { name: tmp, mountPath: /tmp } # 쓰기가 필요한 곳만 볼륨으로
volumes:
- { name: tmp, emptyDir: {} }
$ kubectl exec web -- id
uid=1000 gid=3000 groups=2000
| 설정 | 막는 것 |
|---|---|
runAsUser · runAsNonRoot | root로 실행 |
capabilities.drop: ["ALL"] | root가 가진 특수 권한(네트워크 설정 · 시간 변경 · 소유자 무시 등) 전부 |
allowPrivilegeEscalation: false | 실행 중에 setuid 등으로 권한이 높아지는 것 |
readOnlyRootFilesystem | 컨테이너 안에 악성 파일을 쓰는 것 |
privileged: true (반대 방향) | 아무것도 안 막음 — 노드와 거의 같은 권한. CNI · 스토리지 드라이버 같은 시스템 Pod 외엔 쓰지 않아요 |
hostUsers: false를 쓰면 컨테이너 안의 UID 0이 노드에서는 권한 없는 다른 UID로 매핑돼요. 격리가 뚫려도 노드의 root가 아니에요. 최근 버전(1.36)에서 정식이 됐어요. 노드의 커널 · 런타임이 지원해야 해요.
2. Pod Security Admission
모든 개발자가 SecurityContext를 잘 쓰리라 믿을 순 없죠. Pod Security Admission은 Namespace에 라벨 하나만 붙이면 그 Namespace의 Pod가 정해진 보안 수준(Pod Security Standards)을 지키는지 어드미션(7부 09)에서 검사해요. 학습 자료가 나온 뒤 쿠버네티스에 기본 내장된 기능이에요.
$ kubectl label namespace prod \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/warn=restricted
$ kubectl run test --image=nginx -n prod
Error from server (Forbidden): pods "test" is forbidden: violates PodSecurity "restricted:latest":
allowPrivilegeEscalation != false (container "test" must set securityContext.allowPrivilegeEscalation=false),
unrestricted capabilities (container "test" must set securityContext.capabilities.drop=["ALL"]),
runAsNonRoot != true (pod or container "test" must set securityContext.runAsNonRoot=true),
seccompProfile (pod or container "test" must set securityContext.seccompProfile.type to "RuntimeDefault" or "Localhost")
| 수준 | 허용 범위 | 쓰는 곳 |
|---|---|---|
privileged | 제한 없음 | kube-system · CNI · 스토리지 같은 시스템 |
baseline | 알려진 위험(호스트 네트워크 · privileged · hostPath 등)만 막음 | 대부분의 일반 앱 |
restricted | root 금지 · capabilities 전부 제거 · seccomp 필수 등 모범 사례 강제 | 보안이 중요한 Namespace |
| 모드 (라벨 이름) | 위반하면 |
|---|---|
enforce | Pod 생성 거절 |
warn | 만들어지지만 kubectl에 경고 표시 |
audit | 만들어지지만 감사 로그에 기록 |
warn · audit로 어떤 Pod가 걸리는지 보고 나서 enforce를 켜는 게 안전해요.
03. Network Policy
클러스터 안의 Pod는 기본적으로 누구와든 통신할 수 있어요. 웹 Pod 하나가 털리면 그 Pod에서 DB로 바로 접속할 수 있다는 뜻이죠. NetworkPolicy는 Pod 단위의 방화벽이에요. 라벨로 대상 Pod를 고르고, 들어오는(ingress) · 나가는(egress) 트래픽을 허용 목록으로 정해요.
1. 기본은 모두 허용
2. ingress · egress 규칙
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: db-policy
namespace: prod
spec:
podSelector:
matchLabels: { app: db } # 이 정책이 지키는 Pod
policyTypes: ["Ingress"] # 들어오는 것만 제한 (Egress도 쓰면 나가는 것도)
ingress:
- from:
- podSelector:
matchLabels: { app: api } # 같은 Namespace의 api Pod
- namespaceSelector:
matchLabels: { kubernetes.io/metadata.name: monitoring } # 또는 monitoring Namespace 전체
ports:
- { protocol: TCP, port: 3306 }
podSelector와 namespaceSelector가 각자 -로 시작하면 “api Pod 또는 monitoring Namespace”(OR)예요. - 하나 아래에 둘을 같이 쓰면 “monitoring Namespace에 있는 api Pod”(AND)가 돼요. 들여쓰기 한 칸으로 의미가 완전히 바뀌니 시험 · 실무 모두 자주 틀리는 부분이에요.
# 모든 Pod의 모든 트래픽을 일단 막기 (default deny) — 여기에 필요한 것만 허용을 더해 간다
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: prod
spec:
podSelector: {} # Namespace의 모든 Pod
policyTypes: ["Ingress", "Egress"]
---
# egress를 막았다면 DNS는 꼭 열어 준다
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns
namespace: prod
spec:
podSelector: {}
policyTypes: ["Egress"]
egress:
- to:
- namespaceSelector:
matchLabels: { kubernetes.io/metadata.name: kube-system }
podSelector:
matchLabels: { k8s-app: kube-dns }
ports:
- { protocol: UDP, port: 53 }
- { protocol: TCP, port: 53 }
| 알아 둘 규칙 | 내용 |
|---|---|
| 정책은 더하기만 | 여러 정책이 같은 Pod를 고르면 허용 목록이 합쳐져요. “금지” 규칙은 없어요 |
| 고른 순간 기본 거부 | 어떤 정책이든 Pod를 Ingress로 고르면, 그 Pod로 들어오는 건 허용한 것 빼고 전부 막혀요 |
| 응답은 자동 허용 | 허용된 연결의 응답 패킷은 따로 열 필요 없어요 |
| 대상 고르기 | podSelector · namespaceSelector · ipBlock(클러스터 밖 IP 대역) |
3. CNI가 지원해야 동작한다
NetworkPolicy 객체는 API 서버에 저장될 뿐이고, 실제로 패킷을 막는 건 CNI 플러그인(11부 03)이에요. Calico · Cilium은 지원하지만 Flannel만 쓰면 정책을 만들어도 아무 효과가 없고 오류도 안 나요. 학습 자료의 Weave는 지원했지만 개발이 중단됐어요. 정책이 안 먹히면 CNI부터 확인해요.
$ kubectl get networkpolicy -n prod
$ kubectl describe networkpolicy db-policy -n prod
$ kubectl exec -n prod web -- nc -zv -w 2 db 3306 # 실제로 막히는지 시험
nc: db (10.96.33.21:3306): Connection timed out
4. 8부 셀프 체크
비공개 레지스트리 이미지를 받으려면 무엇이 필요하고 어디에 넣나요?
docker-registry타입 Secret을 만들고 Pod의imagePullSecrets에 넣어요. ServiceAccount에 붙이면 그 SA의 모든 Pod가 써요.로그인 정보가 없어서 이미지를 못 받으면 Pod 상태는?
ErrImagePull→ImagePullBackOff. Events에 401 · unauthorized가 보여요.Pod 수준과 컨테이너 수준 SecurityContext가 둘 다 있으면?
컨테이너 수준이 우선이에요. 그리고 capabilities는 컨테이너 수준에만 있어요.capabilities를 안전하게 주는 방법은?
drop: ["ALL"]로 전부 빼고, 필요한 것만add해요(예:NET_BIND_SERVICE).readOnlyRootFilesystem: true인데 앱이 /tmp에 써야 하면?/tmp에emptyDir볼륨을 마운트해서 그곳만 쓰게 해요.Namespace의 모든 Pod에 restricted 수준을 강제하는 방법은?
Namespace에pod-security.kubernetes.io/enforce=restricted라벨을 붙여요(Pod Security Admission).PodSecurityPolicy는 지금 쓸 수 있나요?
아니요. 1.25에서 제거됐어요. Pod Security Admission이나 Kyverno · Gatekeeper · ValidatingAdmissionPolicy를 써요.NetworkPolicy가 하나도 없을 때 Pod끼리의 통신은?
모두 허용이에요.db Pod에 “api Pod에서 3306만 허용” ingress 정책을 걸면 web → db는?
막혀요. 정책이 db를 고른 순간 허용 목록에 없는 들어오는 연결은 전부 거부돼요.from 아래 podSelector와 namespaceSelector를 같은
-에 쓰는 것과 따로 쓰는 것의 차이는?같은-면 AND(그 Namespace의 그 Pod), 따로면 OR(그 Pod 또는 그 Namespace)예요.egress를 막았더니 서비스 이름으로 접속이 안 돼요. 왜일까요?
DNS(kube-dns, UDP · TCP 53)로 가는 egress를 열지 않아서 이름 풀이가 실패한 거예요.NetworkPolicy를 만들었는데 아무 효과가 없어요. 먼저 의심할 곳은?
CNI 플러그인이 NetworkPolicy를 지원하는지예요(Flannel만으로는 안 돼요).
01. 컨테이너 스토리지 기초
컨테이너는 지워지면 안의 데이터도 같이 사라져요. 왜 그런지, 그럼 데이터를 어디에 둬야 하는지를 컨테이너 수준부터 쿠버네티스 수준까지 차례로 올라가며 봐요. 입문 6부 03 · 04의 복습이자 확장이에요.
1. Docker의 레이어와 스토리지 드라이버
| 용어 | 뜻 |
|---|---|
| 이미지 레이어 | Dockerfile 한 줄마다 생기는 읽기 전용 층 (입문 1부 04). 같은 레이어는 컨테이너 여러 개가 공유 |
| 컨테이너 레이어 | 컨테이너마다 생기는 얇은 쓰기 층. 컨테이너를 지우면 사라짐 |
| copy-on-write | 이미지의 파일을 고치면 쓰기 층으로 복사한 뒤 고침 — 원본 이미지는 그대로 |
| 스토리지 드라이버 | 이 층들을 하나로 겹쳐 보여 주는 장치. 지금은 거의 overlay2(overlayfs). containerd에서는 snapshotter라고 불러요 |
| 볼륨 | 층 바깥의 저장 공간을 컨테이너 안 경로에 연결. 컨테이너가 지워져도 남음 |
$ docker volume create data_volume # /var/lib/docker/volumes/data_volume
$ docker run -v data_volume:/var/lib/mysql mysql # 볼륨 마운트
$ docker run --mount type=bind,source=/data/mysql,target=/var/lib/mysql mysql # 호스트 폴더 그대로 (bind mount)
2. Volume Driver Plugins
볼륨을 실제로 어디에 만들지는 볼륨 드라이버가 정해요. 기본(local)은 그 서버의 디스크이고, 플러그인을 쓰면 클라우드 디스크 · NAS 같은 외부 저장소에 만들 수 있어요. 컨테이너가 다른 서버로 옮겨 가도 데이터가 따라가게 하려면 이런 외부 저장소가 필요해요. 쿠버네티스에서는 이 역할을 다음 절의 CSI 드라이버가 맡아요.
02. CSI
1. Container Storage Interface
1부 02의 CRI가 “런타임을 꽂는 규격”이었던 것처럼, CSI는 “저장소를 꽂는 규격”이에요(네트워크는 CNI, 11부). 예전에는 AWS · GCP 디스크 지원 코드가 쿠버네티스 본체 안에 들어 있었지만(in-tree), 지금은 저장소 회사가 만든 CSI 드라이버를 설치해서 써요.
| CSI 호출 | 누가 | 하는 일 |
|---|---|---|
CreateVolume · DeleteVolume | 컨트롤러 플러그인 (+ external-provisioner) | 저장소에 디스크 만들기 · 지우기 |
ControllerPublishVolume | 컨트롤러 플러그인 (+ external-attacher) | 디스크를 노드에 붙이기 (attach) |
NodeStageVolume · NodePublishVolume | 노드 플러그인 (DaemonSet) | 노드에서 포맷 · 마운트하고 Pod 경로에 연결 |
ControllerExpandVolume | 컨트롤러 플러그인 (+ external-resizer) | 디스크 크기 늘리기 (아래 2) |
$ kubectl get csidrivers
NAME ATTACHREQUIRED PODINFOONMOUNT MODES
pd.csi.storage.gke.io true false Persistent
$ kubectl get pods -n kube-system | grep csi # 컨트롤러(Deployment) + 노드(DaemonSet)
2. CSI Volume Expansion
디스크가 꽉 찼을 때 데이터를 옮기지 않고 크기만 늘릴 수 있어요. StorageClass가 allowVolumeExpansion: true이고 CSI 드라이버가 지원하면, PVC의 요청 크기만 늘리면 돼요.
$ kubectl patch pvc data -p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}}'
$ kubectl get pvc data
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS
data Bound pvc-7d… 20Gi RWO standard-rwo
03. Volume 복습
쿠버네티스 Volume은 Pod 단위예요. 같은 Pod의 컨테이너끼리 공유하고, 종류에 따라 Pod보다 오래 살기도 해요. 어떤 볼륨이 언제까지 사는지가 핵심이에요.
| 종류 | 데이터가 사는 곳 | 언제까지 | 주로 |
|---|---|---|---|
emptyDir | 노드 디스크(또는 메모리) | Pod가 지워질 때까지 | 컨테이너끼리 공유 · 임시 파일 (5부 05 사이드카) |
hostPath | 노드의 특정 폴더 | 노드가 있는 한 — 다른 노드에선 안 보임 | 노드 로그 · 실습. 보안 위험이 커서 일반 앱엔 X (8부 02) |
configMap · secret · projected | API 객체 | 객체가 있는 한 | 설정 · 토큰 (5부 03 · 7부 08) |
persistentVolumeClaim | 외부 저장소 디스크 | PVC를 지울 때까지 (Pod와 무관) | DB · 업로드 파일 |
04. PV와 PVC
PV(PersistentVolume)는 클러스터에 준비된 실제 저장 공간, PVC(PersistentVolumeClaim)는 “이런 저장 공간이 필요해요”라는 요청서예요(입문 6부 04). 관리자는 PV를, 개발자는 PVC를 다루도록 역할이 나뉘어요. PV는 Namespace에 속하지 않고, PVC는 Namespace에 속해요.
1. 접근 모드와 반납 정책
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-20g
spec:
capacity: { storage: 20Gi }
accessModes: ["ReadWriteOnce"]
persistentVolumeReclaimPolicy: Retain
storageClassName: manual
hostPath: { path: /mnt/data } # 실습용. 운영은 csi: 드라이버
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: manual
resources:
requests: { storage: 8Gi }
| 접근 모드 | 뜻 |
|---|---|
ReadWriteOnce (RWO) | 한 노드에서 읽기 · 쓰기 (그 노드의 Pod 여럿은 가능). 클라우드 블록 디스크 대부분 |
ReadOnlyMany (ROX) | 여러 노드에서 읽기만 |
ReadWriteMany (RWX) | 여러 노드에서 읽기 · 쓰기 — NFS · 파일 스토리지 |
ReadWriteOncePod (RWOP) | Pod 하나만 읽기 · 쓰기 (1.29 GA) |
| 반납 정책 | PVC를 지우면 |
|---|---|
Retain | PV는 Released로 남고 데이터도 남아요. 다른 PVC가 바로 쓸 수는 없고, 관리자가 정리해야 해요 |
Delete | PV와 실제 디스크까지 지워요. 동적 프로비저닝(05)의 기본값 |
Recycle | 안의 파일만 지우고 재사용 — 지원 중단, 학습 자료에 나오면 옛 방식 |
2. Pod에서 PVC 쓰기
spec:
containers:
- name: mysql
image: mysql:8.4
volumeMounts:
- name: data
mountPath: /var/lib/mysql
volumes:
- name: data
persistentVolumeClaim:
claimName: data
$ kubectl get pv,pvc
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS
persistentvolume/pv-20g 20Gi RWO Retain Bound default/data manual
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS
persistentvolumeclaim/data Bound pv-20g 20Gi RWO manual
Pending이면 크기 · 접근 모드 · storageClassName이 맞는 PV가 없는 거예요(kubectl describe pvc의 Events). 반대로 Pod가 쓰고 있는 PVC를 지우면 데이터를 지키려고 Terminating에서 멈춰요(PVC 보호). Pod를 먼저 지우면 그때 지워져요.
05. StorageClass
1. 동적 프로비저닝
PV를 관리자가 미리 만들어 두는 방식(정적)은 사람이 계속 일해야 해요. StorageClass는 “이 종류의 저장소는 이 드라이버로 이렇게 만들어라”라는 틀이에요. PVC가 StorageClass를 지정하면 CSI 드라이버가 그때 디스크와 PV를 자동으로 만들어요. 입문 6부에서 GKE의 standard-rwo로 했던 게 이거예요.
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast
annotations:
storageclass.kubernetes.io/is-default-class: "true" # storageClassName 없는 PVC의 기본값
provisioner: pd.csi.storage.gke.io # 어떤 CSI 드라이버로
parameters:
type: pd-ssd # 드라이버별 옵션
reclaimPolicy: Delete # 기본 Delete
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer
2. volumeBindingMode
| 모드 | 디스크를 만드는 때 | 문제 · 장점 |
|---|---|---|
Immediate | PVC를 만드는 즉시 | Pod가 다른 영역(zone)에 배치되면 디스크를 붙일 수 없어 Pending |
WaitForFirstConsumer | 그 PVC를 쓰는 Pod가 스케줄될 때 | Pod가 갈 노드의 영역에 만들어서 안전. 그 전까진 PVC가 Pending인 게 정상 |
$ kubectl get storageclass
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION
standard-rwo (default) pd.csi.storage.gke.io Delete WaitForFirstConsumer true
premium-rwo pd.csi.storage.gke.io Delete WaitForFirstConsumer true
06. 상태 있는 앱의 스토리지
DB처럼 Pod마다 자기 데이터가 있어야 하는 앱은 Deployment + PVC 하나로는 안 돼요(레플리카 전부가 같은 PVC를 나눠 써야 하니까요). 학습 목차엔 없지만 스토리지와 떼어 놓을 수 없어 넣었어요.
1. StatefulSet과 volumeClaimTemplates
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: db
spec:
serviceName: db # headless Service (11부 05)
replicas: 3
selector: { matchLabels: { app: db } }
template:
metadata: { labels: { app: db } }
spec:
containers:
- name: mysql
image: mysql:8.4
volumeMounts:
- { name: data, mountPath: /var/lib/mysql }
volumeClaimTemplates: # Pod마다 이 모양으로 PVC를 하나씩
- metadata: { name: data } # → data-db-0, data-db-1, data-db-2
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: fast
resources: { requests: { storage: 10Gi } }
persistentVolumeClaimRetentionPolicy(whenDeleted · whenScaled를 Delete로)를 써요.
2. VolumeSnapshot
6부 03에서 etcd 스냅샷에는 볼륨 데이터가 없다고 했죠. 볼륨 자체는 VolumeSnapshot으로 저장소 수준의 스냅샷을 떠요. CSI 드라이버가 지원해야 하고, 스냅샷 컨트롤러가 설치돼 있어야 해요.
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
name: data-db-0-snap
spec:
volumeSnapshotClassName: csi-snapclass
source:
persistentVolumeClaimName: data-db-0
---
apiVersion: v1 # 스냅샷에서 새 PVC로 복원
kind: PersistentVolumeClaim
metadata:
name: data-restore
spec:
storageClassName: fast
dataSource:
name: data-db-0-snap
kind: VolumeSnapshot
apiGroup: snapshot.storage.k8s.io
accessModes: ["ReadWriteOnce"]
resources: { requests: { storage: 10Gi } }
3. 9부 셀프 체크
컨테이너를 지우면 그 안에서 쓴 파일이 사라지는 이유는?
컨테이너마다 생기는 얇은 쓰기 레이어에 저장되고, 컨테이너를 지우면 그 레이어도 함께 지워지기 때문이에요.copy-on-write란?
이미지(읽기 전용) 레이어의 파일을 고칠 때, 쓰기 레이어로 복사한 뒤 고치는 방식이에요. 원본 이미지는 바뀌지 않아요.CRI · CNI · CSI는 각각 무엇을 꽂는 규격인가요?
CRI = 컨테이너 런타임, CNI = 네트워크, CSI = 저장소.PVC 크기를 늘리려면 무엇이 필요한가요? 줄일 수는 있나요?
StorageClass의allowVolumeExpansion: true와 확장을 지원하는 CSI 드라이버가 필요하고, PVC의 storage만 늘리면 돼요. 줄이기는 안 돼요.hostPath 볼륨의 데이터가 Pod 재생성 후 안 보일 수 있는 이유는?
hostPath는 그 노드의 폴더라서, Pod가 다른 노드에 다시 만들어지면 그 노드의 빈 폴더를 보게 돼요.8Gi RWO를 요청한 PVC가 5Gi RWO · 10Gi RWX · 20Gi RWO PV 중 어느 것과 묶이나요?
20Gi RWO. 5Gi는 작고, 10Gi는 RWO 모드가 없어요. PV는 PVC 하나와만 묶이니 남는 12Gi는 다른 PVC가 못 써요.reclaimPolicy Retain인 PV에서 PVC를 지우면?
PV는Released상태로 데이터와 함께 남아요. 다른 PVC가 바로 쓸 수는 없고 관리자가 정리해야 해요.Pod가 쓰고 있는 PVC를 지우면?
PVC 보호 때문에Terminating에서 멈추고, Pod가 사라지면 그때 지워져요.WaitForFirstConsumer를 쓰는 이유는?
Pod가 배치될 노드의 영역(zone)에 디스크를 만들어서, 디스크와 Pod가 다른 영역에 있어 붙지 못하는 문제를 막기 위해서예요.StatefulSet의 volumeClaimTemplates로 만든 PVC 이름 규칙은?
템플릿이름-StatefulSet이름-순번. 예:data-db-0. Pod가 다시 만들어져도 같은 PVC에 붙어요.
01. 스위칭 · 라우팅 · 게이트웨이
쿠버네티스 네트워킹(11부)은 리눅스 네트워크 기능을 조합해서 만들어져 있어요. 그래서 학습 자료도 쿠버네티스 전에 리눅스 네트워크 사전 지식을 먼저 다뤄요. 인프라를 처음 접한다면 이 부가 11부를 이해하는 열쇠예요. 명령은 실습용 리눅스(VM)에서 해 보면 가장 빨리 익어요.
$ ip link # 네트워크 인터페이스 목록 (eth0 …)
$ ip addr add 192.168.1.10/24 dev eth0 # 인터페이스에 IP 주기
$ ip route # 라우팅 표 보기
$ ip route add 192.168.2.0/24 via 192.168.1.1 # 2번 네트워크는 1.1(라우터)로 보내라
$ ip route add default via 192.168.1.1 # 나머지 전부(인터넷 포함)는 여기로 = 기본 게이트웨이
$ cat /proc/sys/net/ipv4/ip_forward # 1이면 이 서버가 다른 인터페이스로 패킷을 넘겨줌
0
$ sysctl -w net.ipv4.ip_forward=1 # 켜기 (영구: /etc/sysctl.conf)
| 용어 | 하는 일 | 비유 |
|---|---|---|
| 스위치 | 같은 네트워크(예: 192.168.1.0/24) 안의 기계끼리 연결 | 한 층의 복도 |
| 라우터 | 서로 다른 네트워크를 연결. 네트워크마다 IP를 하나씩 가짐 | 층과 층을 잇는 계단 |
| 게이트웨이 | “그 네트워크로 가려면 여기로 보내라”의 그 문 (보통 라우터 IP) | 계단 입구 |
| /24 | 앞 24비트가 네트워크 주소 → 192.168.1.0 ~ 192.168.1.255가 한 네트워크 | 같은 층 번호 |
ip_forward=1이 꼭 필요해요. kubeadm 설치 준비(12부 04)에 이 설정이 들어 있는 이유예요.
02. DNS
IP 대신 db · www.google.com 같은 이름으로 접속하려면 이름을 IP로 바꿔 주는 장치가 필요해요. 처음엔 각 서버의 파일로, 서버가 많아지면 DNS 서버로 해결해요.
$ cat /etc/hosts # 이 서버만의 이름표
192.168.1.11 db
$ cat /etc/resolv.conf # 물어볼 DNS 서버 · 붙여 볼 도메인
nameserver 192.168.1.100
search mycompany.com
$ grep hosts /etc/nsswitch.conf # 먼저 볼 곳: files(/etc/hosts) → dns
hosts: files dns
$ nslookup web # DNS에 물어보기 (/etc/hosts는 안 봄)
$ dig web.mycompany.com # 더 자세히
| 레코드 | 뜻 | 예 |
|---|---|---|
A | 이름 → IPv4 | web.mycompany.com → 192.168.1.10 |
AAAA | 이름 → IPv6 | web → 2001:db8::10 |
CNAME | 이름 → 다른 이름 | food.web-server → eat.web-server |
/etc/resolv.conf에는 search default.svc.cluster.local svc.cluster.local cluster.local이 들어 있어요. 그래서 2부 04에서 db라고만 써도 같은 Namespace의 Service가 찾아졌던 거예요(11부 05).
03. 네트워크 네임스페이스
입문 1부 03에서 컨테이너는 네임스페이스로 “보이는 범위”를 나눈다고 했어요. 네트워크 네임스페이스는 인터페이스 · IP · 라우팅 표 · ARP 표를 따로 가진 작은 방이에요. 컨테이너(Pod)마다 이 방이 하나씩 있어요. 직접 만들어 보면 Pod 네트워킹이 그대로 보여요.
# 1) 방 만들기
$ ip netns add red ; ip netns add blue
$ ip netns exec red ip link # (= ip -n red link) 방 안에는 lo만 보임
# 2) 방이 많으면 브리지(가상 스위치) 하나에 모으기
$ ip link add v-net-0 type bridge ; ip link set v-net-0 up
$ ip addr add 192.168.15.5/24 dev v-net-0 # 호스트도 브리지에 IP 하나
# 3) 방마다 veth(가상 랜선) 쌍 — 한쪽은 방 안, 한쪽은 브리지에
$ ip link add veth-red type veth peer name veth-red-br
$ ip link set veth-red netns red
$ ip link set veth-red-br master v-net-0 ; ip link set veth-red-br up
$ ip -n red addr add 192.168.15.1/24 dev veth-red ; ip -n red link set veth-red up
# 4) 밖으로: 방의 기본 게이트웨이 = 브리지, 호스트에서 주소 바꿔 내보내기(NAT)
$ ip -n red route add default via 192.168.15.5
$ iptables -t nat -A POSTROUTING -s 192.168.15.0/24 -j MASQUERADE
| 리눅스 부품 | 정체 | 쿠버네티스에서는 |
|---|---|---|
| network namespace | 네트워크가 분리된 방 | Pod 하나 (Pod 안 컨테이너들은 같은 방을 써서 localhost 공유) |
| veth pair | 양 끝이 이어진 가상 랜선 | Pod 방 ↔ 노드 연결 |
| bridge | 가상 스위치 | 노드 안 Pod들을 한 네트워크로 (예: cni0) |
| iptables NAT | 주소 바꾸기 | Pod → 밖으로 나갈 때 · Service 규칙(1부 07) |
04. Docker 네트워킹
03에서 손으로 한 일을 Docker는 docker run 한 번에 자동으로 해요. 컨테이너마다 네트워크 네임스페이스를 만들고, veth로 docker0 브리지에 꽂고, IP를 주고, NAT를 걸어요.
$ docker run --network none nginx # 네트워크 없음
$ docker run --network host nginx # 호스트 네트워크 그대로 (포트 충돌 주의)
$ docker run -p 8080:80 nginx # 기본 bridge + 호스트 8080 → 컨테이너 80
$ ip addr show docker0
docker0: ... inet 172.17.0.1/16
$ iptables -t nat -L DOCKER -n | grep 8080
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080 to:172.17.0.2:80
05. CNI란 무엇인가
03 · 04에서 본 “네임스페이스 만들고 → veth 연결하고 → 브리지에 꽂고 → IP 주고 → NAT”는 Docker · rkt · containerd 누가 하든 같아요. 그래서 이 과정을 플러그인 하나로 떼어 내고, “런타임은 이렇게 부르고, 플러그인은 이렇게 답한다”는 약속을 정한 게 CNI(Container Network Interface)예요. 1부 02의 CRI, 9부 02의 CSI와 같은 생각이에요.
$ ls /opt/cni/bin # 플러그인 실행 파일
bridge host-local loopback portmap calico …
$ ls /etc/cni/net.d # 어떤 플러그인을 어떤 설정으로 쓸지 (이름순으로 첫 파일)
10-calico.conflist
{
"cniVersion": "1.0.0",
"name": "mynet",
"type": "bridge", # /opt/cni/bin/bridge 를 부른다
"bridge": "cni0",
"isGateway": true,
"ipMasq": true, # 밖으로 나갈 때 NAT
"ipam": {
"type": "host-local", # IP 나눠 주는 플러그인 (IPAM, 11부 03)
"subnet": "10.244.1.0/24",
"routes": [{ "dst": "0.0.0.0/0" }]
}
}
| CNI가 정한 것 | 내용 |
|---|---|
| 런타임의 일 | 네트워크 네임스페이스 만들기 · 설정 파일 읽기 · 플러그인 실행 |
| 플러그인 명령 | ADD(연결) · DEL(정리) · CHECK · VERSION |
| 플러그인의 일 | 인터페이스 만들고 연결 · IP 할당 · 라우트 설정 · 결과(JSON)로 IP 돌려주기 |
--network=none으로 띄운 뒤 CNI를 따로 불렀어요. 지금은 노드에 Docker 대신 containerd가 있고(1부 02), containerd가 CNI를 직접 불러요. 노드에 CNI 플러그인을 설치하는 게 클러스터 설치의 필수 단계인 이유예요(12부 04).
1. 10부 셀프 체크
192.168.1.10이 192.168.2.10과 통신하려면 라우팅 표에 무엇이 필요하나요?
192.168.2.0/24 via <라우터의 1번 네트워크 쪽 IP>같은 경로, 또는 기본 게이트웨이(default via …).리눅스 서버가 한 인터페이스로 받은 패킷을 다른 인터페이스로 넘겨주게 하려면?
/proc/sys/net/ipv4/ip_forward를 1로 (sysctl -w net.ipv4.ip_forward=1)./etc/hosts와 DNS 중 무엇을 먼저 보는지는 어디서 정하나요?
/etc/nsswitch.conf의hosts:줄 (보통files dns).resolv.conf의 search는 무엇을 하나요?
짧은 이름 뒤에 도메인을 붙여서 찾아 줘요.search mycompany.com이면web을web.mycompany.com으로도 찾아요.네트워크 네임스페이스 두 개를 직접 연결하는 가상 장치는?
veth pair(양 끝이 이어진 가상 랜선). 여럿이면 bridge(가상 스위치)에 모아요.네임스페이스 안에서 밖의 네트워크로 나가려면 필요한 두 가지는?
방의 기본 게이트웨이를 브리지 주소로 두는 경로, 그리고 호스트에서 출발지 주소를 바꾸는 NAT(MASQUERADE).docker run -p 8080:80은 내부적으로 무엇을 만드나요?호스트 8080으로 온 패킷을 컨테이너 IP의 80으로 보내는 iptables DNAT 규칙.CNI 플러그인 실행 파일과 설정 파일은 각각 어디에 있나요?
실행 파일/opt/cni/bin, 설정/etc/cni/net.d.CNI에서 런타임과 플러그인의 역할 분담은?
런타임은 네임스페이스를 만들고 플러그인을ADD·DEL로 불러요. 플러그인은 인터페이스 연결 · IP 할당 · 라우트 설정을 하고 결과를 돌려줘요.
01. 클러스터 네트워킹
10부의 리눅스 부품들이 클러스터 안에서 어떻게 조합되는지 봐요. 쿠버네티스 네트워크는 층이 세 개예요. 노드끼리(서버 네트워크), Pod끼리(CNI가 만드는 네트워크), Service(kube-proxy가 만드는 가상 주소). 먼저 맨 아래, 노드와 부품들이 쓰는 포트부터예요.
1. 노드와 컴포넌트가 쓰는 포트
| 포트 | 부품 | 누가 접속하나 |
|---|---|---|
6443 | kube-apiserver | kubectl · 모든 부품 · kubelet |
2379 · 2380 | etcd (클라이언트 · 피어) | API 서버 · 다른 etcd |
10250 | kubelet (모든 노드) | API 서버 (logs · exec · metrics) |
10259 | kube-scheduler | 자기 노드 (상태 확인) |
10257 | kube-controller-manager | 자기 노드 (상태 확인) |
30000-32767 | NodePort Service (2부 03) | 클러스터 밖 사용자 |
2. 실습 환경 살펴보기
학습 자료의 “Explore Environment” 실습에서 자주 묻는 것들이에요. 노드의 IP · 인터페이스 · 기본 경로, 부품이 듣는 포트를 직접 찾아봐요.
$ kubectl get nodes -o wide # INTERNAL-IP = 노드 IP
NAME STATUS ROLES INTERNAL-IP
controlplane Ready control-plane 192.168.0.10
node01 Ready <none> 192.168.0.11
$ ip a | grep -B2 192.168.0.10 # 그 IP가 붙은 인터페이스 (예: eth0)
$ ip link show eth0 # MAC 주소 · 상태
$ ip route show default # 기본 게이트웨이
default via 192.168.0.1 dev eth0
$ ss -tlnp | grep -E "kube|etcd" # 부품이 듣는 포트 (netstat -nplt도 같음)
LISTEN 0 4096 *:6443 *:* users:(("kube-apiserver",…))
LISTEN 0 4096 127.0.0.1:2379 … users:(("etcd",…))
02. Pod 네트워킹
1. 쿠버네티스가 요구하는 네트워크 모델
쿠버네티스는 Pod 네트워크를 직접 만들지 않고 규칙만 정해요. 그 규칙을 지키는 네트워크를 만드는 건 CNI 플러그인(03)의 몫이에요.
| 규칙 | 뜻 |
|---|---|
| 모든 Pod는 자기 IP를 가진다 | Pod 안의 컨테이너끼리는 그 IP를 공유하고 localhost로 통신 (10부 03의 방 하나) |
| 모든 Pod는 다른 모든 Pod와 NAT 없이 통신한다 | 노드가 달라도 Pod IP 그대로 닿아야 함 |
| 노드의 에이전트(kubelet 등)는 그 노드의 Pod와 통신한다 | probe · logs |
2. 노드 사이의 Pod 통신
$ kubectl get node node01 -o jsonpath='{.spec.podCIDR}'
10.244.1.0/24
$ kubectl get pods -o wide # IP 열이 노드별 대역 안에 있음
web-a Running 10.244.1.5 node01
web-b Running 10.244.2.7 node02
$ ip route | grep 10.244 # node01에서: 다른 노드 대역으로 가는 길 (CNI가 넣음)
10.244.2.0/24 via 192.168.0.12 dev eth0
03. CNI 플러그인과 IPAM
1. kubelet이 CNI를 부르는 순서
10부 05에서 본 흐름이 그대로예요. 스케줄러가 Pod를 노드에 배정 → kubelet이 CRI로 containerd에 Pod 생성을 요청 → containerd가 네트워크 네임스페이스를 만들고 /etc/cni/net.d 설정대로 CNI 플러그인을 ADD → Pod IP가 정해져 kubelet이 API 서버에 보고.
--network-plugin=cni
예전에는 kubelet 실행 옵션에 --network-plugin=cni · --cni-conf-dir · --cni-bin-dir가 있었어요. 1.24에서 dockershim과 함께 빠졌고, 지금은 containerd 설정(/etc/containerd/config.toml)에서 CNI 폴더를 정해요. 기본값은 그대로 /etc/cni/net.d · /opt/cni/bin이에요.
2. IPAM: Pod IP 나눠 주기
| 대역 | 정하는 곳 | 예 (kubeadm) |
|---|---|---|
| 노드 IP | 서버 네트워크 | 192.168.0.0/24 |
| Pod 대역 | kubeadm init --pod-network-cidr + CNI 설정 | 10.244.0.0/16 (노드마다 /24) |
| Service 대역 | API 서버 --service-cluster-ip-range | 10.96.0.0/12 |
3. Weave(개발 중단)와 요즘 쓰는 CNI
학습 자료는 실습에 Weave Net을 써요. Weave를 만든 회사(Weaveworks)가 2024년에 문을 닫아 더 이상 개발 · 보안 패치가 없어요. 원리를 배우는 데는 그대로 유효하지만, 새 클러스터에는 아래 중에서 골라요.
| CNI | Pod 간 전달 | NetworkPolicy | 특징 |
|---|---|---|---|
| Flannel | VXLAN 터널 | 없음 | 가장 단순. 실습 · 작은 클러스터 |
| Calico | BGP 경로 또는 VXLAN | 있음 | 널리 쓰임. 터널 없이 경로로 빠르게 |
| Cilium | eBPF (+ VXLAN · 직접 경로) | 있음 (L7까지) | kube-proxy 대체, Hubble로 트래픽 관찰. 요즘 가장 활발 |
| 클라우드 CNI | 클라우드 네트워크 직접 (VPC IP) | 클라우드별 | GKE · EKS · AKS 기본 |
$ ls /etc/cni/net.d # 어떤 CNI가 깔렸나
10-calico.conflist
$ kubectl get pods -n kube-system | grep -E "calico|cilium|flannel|weave"
$ kubectl logs -n kube-system -l k8s-app=calico-node --tail=20
04. Service 네트워킹
1. Service IP는 실제로 어디에도 없다
Pod IP는 실제 네트워크 인터페이스에 붙어 있지만, Service의 ClusterIP는 어떤 인터페이스에도 붙어 있지 않아요. ping도 안 돼요. 그 주소로 가는 패킷을 모든 노드의 kube-proxy가 만든 규칙이 가로채서 Pod IP로 바꿔 줄 뿐이에요(1부 07).
2. kube-proxy 모드와 규칙 읽기
$ kubectl get svc web-svc
NAME TYPE CLUSTER-IP PORT(S)
web-svc ClusterIP 10.96.120.15 80/TCP
$ grep service-cluster-ip-range /etc/kubernetes/manifests/kube-apiserver.yaml
- --service-cluster-ip-range=10.96.0.0/12
$ iptables -t nat -L KUBE-SERVICES -n | grep web-svc
KUBE-SVC-W5RQ3PZ2LJ6YT4AB tcp -- 0.0.0.0/0 10.96.120.15 /* default/web-svc */ tcp dpt:80
$ iptables -t nat -L KUBE-SVC-W5RQ3PZ2LJ6YT4AB -n
KUBE-SEP-AAA… all -- 0.0.0.0/0 0.0.0.0/0 statistic mode random probability 0.50000000000
KUBE-SEP-BBB… all -- 0.0.0.0/0 0.0.0.0/0
$ iptables -t nat -L KUBE-SEP-AAA… -n
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp to:10.244.1.5:8080
$ kubectl logs -n kube-system -l k8s-app=kube-proxy | grep -i "proxier" # 어떤 모드인지
| 체인 | 역할 |
|---|---|
KUBE-SERVICES | “이 ClusterIP:포트면 → 그 Service 체인으로” |
KUBE-SVC-… | Pod가 N개면 확률로 하나를 고름 (1/N, 1/(N-1) …) |
KUBE-SEP-… | 골라진 Pod IP:포트로 목적지 바꾸기(DNAT) |
KUBE-NODEPORTS | NodePort로 온 패킷 → 같은 Service 체인으로 |
05. 클러스터 DNS와 CoreDNS
1. Service와 Pod의 DNS 이름
입문 5부 03에서 Service 이름으로 접속했어요. 그 이름을 IP로 바꿔 주는 게 kube-system의 CoreDNS예요. 10부 02의 DNS 서버 역할을 클러스터 안에서 하는 거죠.
| 대상 | 이름 | 답 |
|---|---|---|
| Service | db.prod.svc.cluster.local (같은 Namespace면 db) | ClusterIP |
headless Service (clusterIP: None) | db.prod.svc.cluster.local | Pod IP들 |
| StatefulSet Pod | db-0.db.prod.svc.cluster.local | 그 Pod IP (9부 06) |
| Pod (설정 시) | 10-244-1-5.prod.pod.cluster.local | 그 Pod IP |
$ kubectl exec -it web -- cat /etc/resolv.conf
nameserver 10.96.0.10 ← kube-dns Service
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
$ kubectl exec -it web -- nslookup db.prod
Name: db.prod.svc.cluster.local
Address: 10.96.33.21
2. CoreDNS 설정 (Corefile)
$ kubectl get deploy,svc -n kube-system -l k8s-app=kube-dns
deployment.apps/coredns 2/2
service/kube-dns ClusterIP 10.96.0.10 53/UDP,53/TCP,9153/TCP
$ kubectl get configmap coredns -n kube-system -o yaml
Corefile: |
.:53 {
errors
health
ready
kubernetes cluster.local in-addr.arpa ip6.arpa { # 클러스터 이름은 API를 보고 답함
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
prometheus :9153
forward . /etc/resolv.conf # 나머지는 노드의 DNS로
cache 30
loop
reload
loadbalance
}
$ grep -A1 clusterDNS /var/lib/kubelet/config.yaml # kubelet이 Pod에 넣어 주는 DNS 주소
clusterDNS:
- 10.96.0.10
www.google.com을 찾으면 www.google.com.default.svc.cluster.local 같은 실패 질문을 몇 번 거친 뒤에 진짜 이름을 물어요. 바깥 도메인을 많이 부르는 앱은 이름 끝에 점(www.google.com.)을 붙이면 빨라져요.
06. Ingress
LoadBalancer Service는 Service마다 외부 IP(그리고 비용)가 하나씩 생겨요. Ingress는 입구 하나에서 호스트 · 경로를 보고 여러 Service로 나눠 줘요(입문 5부 04). 입문에서는 GKE가 컨트롤러를 대신 만들어 줬지만, 직접 설치한 클러스터에서는 컨트롤러부터 설치해야 해요.
1. Ingress와 Ingress Controller
2. 경로 · 호스트 규칙
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: shop
namespace: shop
annotations:
nginx.ingress.kubernetes.io/rewrite-target: / # /wear → 뒤 Service에는 / 로 (컨트롤러 전용 옵션)
spec:
ingressClassName: nginx # 어느 컨트롤러가 처리할지
tls:
- hosts: [shop.example.com]
secretName: shop-tls # type: kubernetes.io/tls Secret
defaultBackend:
service: { name: not-found, port: { number: 80 } }
rules:
- host: shop.example.com
http:
paths:
- path: /wear
pathType: Prefix # Prefix · Exact · ImplementationSpecific
backend:
service: { name: wear-svc, port: { number: 80 } }
- path: /watch
pathType: Prefix
backend:
service: { name: watch-svc, port: { number: 80 } }
$ kubectl create ingress shop -n shop --class=nginx \
--rule="shop.example.com/wear*=wear-svc:80" --rule="shop.example.com/watch*=watch-svc:80"
$ kubectl get ingressclass
$ kubectl describe ingress shop -n shop # Rules · Backends 확인
3. Ingress NGINX 은퇴
kubernetes/ingress-nginx)는 2026년 3월로 유지보수가 끝났어요. 이미 돌고 있는 것은 계속 동작하지만 더 이상 보안 패치가 나오지 않아요. 할 일은 둘 중 하나예요. ① 다른 Ingress 컨트롤러(Traefik · HAProxy · F5 NGINX · 클라우드 컨트롤러 등)로 교체 — Ingress API 자체는 그대로 쓸 수 있어요. ② 다음 절의 Gateway API로 이전 — 쿠버네티스가 권장하는 방향이에요. 시험 · 실습에서 Ingress 문법은 여전히 나오니 알아 둬야 해요.
07. Gateway API
Ingress는 기능이 적어서(헤더 조건 · 트래픽 비율 나누기 등) 결국 컨트롤러마다 다른 어노테이션으로 기능을 붙였어요. 같은 YAML이 컨트롤러를 바꾸면 안 돌았죠. Gateway API는 이런 기능을 표준 필드로 넣고, 리소스를 역할별로 나눈 후속 표준이에요.
1. GatewayClass · Gateway · HTTPRoute
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway # 운영자: 입구
metadata:
name: shop-gw
namespace: infra
spec:
gatewayClassName: envoy # 인프라 담당이 설치한 구현 (Envoy Gateway · Istio · Cilium · NGINX Gateway Fabric · GKE …)
listeners:
- name: https
protocol: HTTPS
port: 443
hostname: shop.example.com
tls:
certificateRefs: [{ name: shop-tls }]
allowedRoutes:
namespaces: { from: All } # 다른 Namespace의 Route도 붙을 수 있게
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute # 개발자: 경로
metadata:
name: wear
namespace: shop
spec:
parentRefs:
- { name: shop-gw, namespace: infra }
rules:
- matches:
- path: { type: PathPrefix, value: /wear }
backendRefs:
- { name: wear-v1, port: 80, weight: 90 } # 카나리: 90%
- { name: wear-v2, port: 80, weight: 10 } # 10%
- matches:
- headers: [{ name: x-beta, value: "true" }]
backendRefs:
- { name: wear-v2, port: 80 }
2. Ingress와 무엇이 다른가
| Ingress | Gateway API | |
|---|---|---|
| 리소스 | Ingress 하나에 입구 + 규칙 | GatewayClass · Gateway(입구) · HTTPRoute(규칙) 분리 |
| 역할 | 한 사람이 다 씀 | 인프라 · 운영 · 개발이 각자 자기 것 |
| 고급 기능 | 컨트롤러별 어노테이션 | 가중치 · 헤더 매칭 · 리다이렉트 등 표준 필드 |
| 프로토콜 | HTTP(S) | HTTP · gRPC(GRPCRoute) · TLS · TCP · UDP |
| 상태 | 기능 추가 중단(유지만) | v1 GA, 계속 발전 — 새로 만든다면 이쪽 |
$ kubectl get gatewayclass,gateway,httproute -A
$ kubectl describe httproute wear -n shop # Parents: Accepted · ResolvedRefs 조건 확인
ingress2gateway 변환 도구를 쓸 수 있어요.
3. 11부 셀프 체크
API 서버 · etcd · kubelet이 쓰는 포트는?
API 서버 6443, etcd 2379(클라이언트) · 2380(피어), kubelet 10250.쿠버네티스 네트워크 모델의 핵심 규칙 두 가지는?
모든 Pod는 자기 IP를 가진다, 모든 Pod는 다른 노드의 Pod와도 NAT 없이 통신한다.node01의 Pod가 node02의 Pod로 가는 방법 두 가지는?
노드마다 상대 노드의 Pod 대역으로 가는 경로를 두거나(Calico BGP 등), 패킷을 노드 IP로 감싸는 터널(VXLAN)을 써요(Flannel 등).Pod 대역 · Service 대역 · 노드 대역은 각각 어디서 정하나요?
Pod:--pod-network-cidr+ CNI 설정, Service: API 서버--service-cluster-ip-range, 노드: 서버 네트워크. 셋은 겹치면 안 돼요.학습 자료의 Weave Net을 새 클러스터에 쓰지 않는 이유는?
만든 회사가 문을 닫아 개발 · 보안 패치가 중단됐기 때문이에요. Calico · Cilium · Flannel 등을 써요.ClusterIP로 ping이 안 되는 이유는?
ClusterIP는 어떤 인터페이스에도 붙어 있지 않은 가상 주소이고, kube-proxy 규칙이 특정 포트의 TCP · UDP 패킷만 Pod로 바꿔 주기 때문이에요.iptables 모드에서 Service가 Pod를 고르는 방법은?
KUBE-SVC체인이 확률(statistic random)로KUBE-SEP체인 하나를 고르고, 거기서 Pod IP로 DNAT해요.Pod의 /etc/resolv.conf에 있는 nameserver는 누구인가요?
kube-dns Service(보통 10.96.0.10)이고, 뒤에 CoreDNS Pod들이 있어요. kubelet 설정의 clusterDNS 값이 들어가요.CoreDNS 설정은 어디에 있고, 클러스터 밖 이름은 어떻게 처리하나요?
kube-system의 ConfigMapcoredns의 Corefile. 클러스터 밖 이름은forward . /etc/resolv.conf로 노드의 DNS에 넘겨요.Ingress를 만들었는데 아무 일도 안 일어나요. 무엇부터 보나요?
Ingress Controller가 설치돼 있는지,ingressClassName이 그 컨트롤러를 가리키는지 확인해요.Ingress NGINX 유지보수 종료 후의 선택지 두 가지는?
다른 Ingress 컨트롤러로 교체(Ingress API는 그대로), 또는 Gateway API로 이전.Gateway API에서 Gateway와 HTTPRoute는 각각 누가 관리하나요?
Gateway(입구 · 포트 · 인증서)는 클러스터 운영자, HTTPRoute(경로 · 백엔드)는 앱 개발자. GatewayClass는 인프라 담당이 정해요.