08. 쿠버네티스 핵심 개념 (3)
이 문서는 인프런 강의 "데브옵스를 위한 쿠버네티스 마스터"을 듣고 작성되었습니다. 최대한 요약해서 강의 내용을 최소로 하는데 목표를 두고 있어서, 더 친절하고 정확한 내용을 원하신다면 강의를 구매하시는 것을 추천드립니다. => 강의 링크
네임스페이스
namespace
란 물리 쿠버네티스 클러스터 내에서 동작하는 가상 쿠버네티스 클러스터이다. 약간 실제 물리서버에서 동작하는 VM
과 성격이 비슷한데, 쉽게 생각해서 리소스들을 분리된 영역으로 격리시켜 관리하는 기술이다.
기본적으로 쿠버네티스 클러스터에는 다음의 4가지 네임스페이스가 자동으로 생성된다.
$ kubectl get namespace
NAME STATUS AGE
default Active 15d
kube-node-lease Active 15d
kube-public Active 15d
kube-system Active 15d
이제 namespace
하나를 생성해보자. 터미널에 다음과 같이 ns-test.yaml
을 만든다.
apiVersion: v1
kind: Namespace
metadata:
creationTimestamp: null
name: ns-test
spec: {}
status: {}
이제 터미널에 다음을 입력한다.
$ kubectl create -f ns-test.yaml
namespace/ns-test created
namespace
가 잘 만들어졌는지 확인해보자.
$ kubectl get ns
NAME STATUS AGE
default Active 15d
kube-node-lease Active 15d
kube-public Active 15d
kube-system Active 15d
# 추가되었다.
ns-test Active 39s
이제 namespace
안에 pod
을 하나 만들어보자. 터미널에 다음을 입력한다.
$ kubectl run nginx --image=nginx -n ns-test
pod/nginx created
이제 pod
이 잘 생성되었는지 확인해보자.
$ kubectl get pod
No resources found in default namespace.
분명 리소스를 생성했는데 안보인다. 왜냐하면, 기본적으로 namespace
는 "default"로 잡혀있다. 내가 원하는 namespace
의 리소스를 확인하고 싶다면 다음과 같이 입력하면 된다.
# kubectl get <resource> -n <namespace>
$ kubectl get pod -n ns-test
NAME READY STATUS RESTARTS AGE
nginx 1/1 Running 0 5m8s
namespace
역시 다음과 같이 삭제할 수 있다.
$ kubectl delete -f ns-test.yaml
namespace "ns-test" deleted
서비스
Service
란 Pod
집합에서 실행 중인 애플리케이션을 네트워크 서비스로 노출하는 추상화된 방법이다. Pod
는 일시적으로 생성되는 컨테이너의 집합인데, 실제 리소스 생성/수정/삭제 시에, IP 주소가 지속적으로 변동되기 떄문에 이를 추적하고 부하를 분산시켜야 한다. 이를 해결하기 위한 것이 바로 Service
이다.
서비스는 다음과 같이 구성할 수 있다.
src/ch08/k8s/simple-app-svc-v1.yaml
# service
apiVersion: v1
kind: Service
metadata:
name: simple-app-svc
spec:
sessionAffinity: ClientIP
ports:
- port: 80
targetPort: 8080
selector:
app: simple-app
---
# deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: simple-app-dep
labels:
app: simple-app
spec:
replicas: 3
selector:
matchLabels:
app: simple-app
template:
metadata:
labels:
app: simple-app
spec:
containers:
- name: simple-app
image: gurumee92/simple-app:v1
ports:
- containerPort: 8080
위의 코드는 Service
이고, 아래 ---
아래 코드는 Deployment
이다. 즉 위의 코드는 simple-app
컨테이너 3개를 구동시키는 Deployment
와, 이를 외부로 노출시키는 Service
를 구동시키는 것이다. 다음 명령어로 만들 수 있다.
$ pwd
/home/gurumee/k8s/ch08
$ kubectl create -f simple-app-svc.yaml
service/simple-app-svc created
deployment.apps/simple-app-dep created
다음 명령어로 생성된 Service
목록을 확인할 수 있다.
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 6d11h
simple-app-svc ClusterIP 10.105.112.199 <none> 80/TCP 50s
여기서 생성된 Pod
들을 보자.
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
simple-app-dep-5d4c8d678b-hcqzj 1/1 Running 0 21m 10.32.0.3 slave2 <none> <none>
simple-app-dep-5d4c8d678b-jljj2 1/1 Running 0 21m 10.32.0.2 slave2 <none> <none>
simple-app-dep-5d4c8d678b-p5sdn 1/1 Running 0 21m 10.32.0.4 slave2 <none> <none>
이 때 IP를 기억해두자. 서비스의 정보를 확인한다.
$ kubectl describe svc simple-app-svc
Name: simple-app-svc
Namespace: default
Labels: <none>
Annotations: <none>
Selector: app=simple-app
# 기본 타입은 ClusterIP이다.
Type: ClusterIP
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.105.112.199
IPs: 10.105.112.199
Port: <unset> 80/TCP
TargetPort: 8080/TCP
# 엔드포인트에 Pod들의 IP가 할당된 것을 확인할 수 있다.
Endpoints: 10.32.0.2:8080,10.32.0.3:8080,10.32.0.4:8080
Session Affinity: ClientIP
Events: <none>
여기서 Endpoints
에 Pod
들의 IP가 할당된 것을 확인할 수 있다. 또한 아무 타입 지정을 하지 않으면 ClusterIP
로 지정된다. 이게 모든 서비스의 원형 타입이라고 생각하면 된다. 현재 이렇게 분산된 경우, 한 가지만 더 알아두자. 현재 설정으로는 서비스 세션이 고정되지 않는다. 이 때 세션을 고정하기 위한 설정이 있는데 바로 sessionAffinity
이다. 다음과 같이 수정한다.
src/ch08/k8s/simple-app-svc-v2.yaml
apiVersion: v1
kind: Service
metadata:
name: simple-app-svc
spec:
# 세션을 고정하기 위한 설정
sessionAffinity: ClientIP
# ...
# ...
이렇게 하면, 한 번 요청을 한 경우, 이후부터의 요청은 최초 요청한 Pod
에게 요청을 지속적으로 발생하게 된다. 또한 외부 IP와 연결이 필요한 경우는 Service
와 더불어서 Endpoints
라는 리소스가 필요하다. 이런 식으로 외부 IP와 연동할 수 있다.
서비스
apiVersion: v1
kind: Service
metadata:
name: external-service
spec:
ports:
- port: 80
엔드포인트
apiVersion: v1
kind: Endpoints
metadata:
name: external-service
subnets:
- addresses:
- ip: 11.11.11.11
ip: 22.22.22.22
ports:
- port: 80
다음 절부터는 VM
이 아닌 GCP
혹은 EKS
등의 Public Cloud가 제공하는 쿠버네티스 클러스터를 이용하자.
서비스를 expose하는 방법 (1) NodePort
서비스를 노출하는 3가지 방법 중 하나이다. 노드 자체 포트를 사용하여 Pod
로 리다이렉트 한다. 다음과 같이 서비스 파일을 생성한다.
src/k8s/ch08/simple-app-np.yaml
apiVersion: v1
kind: Service
metadata:
name: simple-app-np
spec:
type: NodePort
selector:
app: simple-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
nodePort: 30001
위에서 type
은 NodePort
로 지정되고, ports
에서 nodePort
가 "30001"로 지정된 것을 확인할 수 있다. 터미널에서 다음과 같이 입력하면 서비스를 생성할 수 있다.
$ kubectl create -f simple-app-np.yaml
service/simple-app-np created
또한 서비스 상태를 확인해보자.
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.112.0.1 <none> 443/TCP 7m26s
simple-app-np NodePort 10.112.13.203 <none> 80:30001/TCP 34s
여기서 80:30001 로 묶여 있는데, Compute Engine
대시보드로 가보면 다음 노드들이 구성된 것을 확인할 수 있다.
이 때 해당 노드 IP:nodePort로 구동되는 Pod
들을 호출할 수 있다. 호출 이전에 방화벽 정책을 통해 NodePort
에 대해서 외부로 공개해주어야 한다.
$ gcloud compute firewall-rules create simple-app-svc-rules --allow=tcp:30001
그 후 노드 IP:노드 포트로 요청을 할 수가 있다.
# curl <노드 IP>:<노드 포트>
$ curl 34.64.75.5:30001
Hello World v1%
$ curl 34.64.132.245:30001
Hello World v1%
$ curl 34.64.143.114:30001
Hello World v1%
서비스를 expose하는 방법 (2) LoadBalancer
서비스를 노출하는 3가지 방법 중 하나이다. 외부 게이트웨이를 사용해 노드 포트로 리다이렉트 한다.
src/ch08/k8s/simple-app-lb.yaml
apiVersion: v1
kind: Service
metadata:
name: simple-app-lb
spec:
type: LoadBalancer
selector:
app: simple-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
여기서 type
이 LoadBalancer
인 것을 알아두자. 그리고 nodePort
를 지정하지 않으면 30000 번대 포트에서 무작위로 선택이 된다. 한 번 만들어두자.
$ kubectl create -f simple-app-lb.yaml
service/simple-app-lb created
그 다음 시간이 좀 지나서 확인해보면 다음과 같이 생성됨을 확인할 수 있다.
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.112.0.1 <none> 443/TCP 5m59s
simple-app-lb LoadBalancer 10.112.15.122 34.64.122.149 80:31694/TCP 74s
여기에 EXTERNAL-IP
에 IP가 할당된 것을 확인할 수 있다. 애는 GCP
네트워크 서비스에서 제공하는 "부하 분산" 즉 LoadBalacner
이다. 다음과 같이 확인할 수 있다.
외부 IP가 동일하다. 이제 이 IP로 호출할 수 있다. 이떄는 nodePort
가 아닌 port
로 지정된 포트 번호로 호출해야 한다.
$ curl 34.64.122.149:80
Hello World v1%
서비스를 expose하는 방법 (3) Ingress
서비스를 노출하는 3가지 방법 중 하나이다. 하나의 IP 주소를 통해 여러 서비스를 제공하는 특별한 메커니즘이다. 인그레스 방법은 설정이 조금 빡세다. 먼저 NodePort
용 서비스를 생성해둔다.
$ kubectl create -f simple-app-np.yaml
service/simple-app-np created
그 후 다음과 같이 인그레스를 만드는 설정 파일을 생성한다.
src/ch08/k8s/simple-app-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: simple-app-ingress
spec:
rules:
- host: gurumee.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: simple-app-np
port:
number: 80
host
에 Pod
들이 구성된 도메인을 연결해준다. 그 후 http.paths.path
를 지정해주고 https.paths.backend.service.name
에 Pod
을 연결해준다. 이제 터미널에 다음을 입력하여 인그레스를 생성한다.
$ kubectl create -f simple-app-ingress.yaml
ingress.networking.k8s.io/simple-app-ingress created
다음과 같이 확인할 수 있다.
$ kubectl get ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
simple-app-ingress <none> gurumee.com 34.149.182.51 80 94s
이제 이 주소로 curl을 날려보자. (여기서는 kubectl이 실행되는 터미널에서 진행해야 한다.)
$ curl 34.149.182.51
response 404 (backend NotFound), service rules for the path non-existent
gurumee.com
이 없기 때문에 404가 뜬다. 이걸 허용하려면 원래 도메인 설정이 필요하나 귀찮으니까 /etc/hosts
에서 다음과 같이 수정한다.
# Kubernetes-managed hosts file.
127.0.0.1 localhost
# 수정된 곳
# ingress IP 매핑된 host
34.149.182.51 gurumee.com
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
fe00::0 ip6-mcastprefix
fe00::1 ip6-allnodes
fe00::2 ip6-allrouters
172.17.0.4 cs-595878838943-default-boost-l8dqx
그 후 이제는 "Host"에 curl
을 날려보자.
$ curl gurumee.com
Hello World v1
kubectl
이 실행되는 터미널이 아니라면 어차피 외부에 지정된 도메인이 아니므로 이런식으로 호출은 불가능하다.