파드 집합에서 실행중인 애플리케이션을 네트워크 서비스로 노출하는 추상화 방법이다.
쿠버네티스를 사용하면 익숙하지 않은 서비스 디스커버리 메커니즘을 사용하기 위해 애플리케이션을 수정할 필요가 없다. 쿠버네티스는 파드에게 고유한 IP 주소와 파드 집합에 대한 단일 DNS 명을 부여하고, 그것들 간에 로드-밸런스를 수행할 수 있다.
서비스라고 하는것은 프록시를 설정하는 것이라 볼 수 있다. 이 프록시도 컨테이너는 아니기에 컴퓨팅 자원이 아닌 네트워크의 가상화된 기능이라고 보면 된다. 즉 파드가 프록시를 통해서 타켓파드에 접근한다고 보면 된다.
파드는 비영구적 리소스이다. 만약 앱을 실행하기 위해 디플로이먼트를 사용한다면, 동적으로 파드를 생성하고 제거할 수 있다.
각 파드는 고유한 IP 주소를 갖지만, 디플로이먼트에서는 한 시점에 실행되는 파드 집합이 잠시 후 실행되는 해당 파드 집합과 다를 수 있다.
cd ../../05_network/01_internal_service/
vi myapp-svc.yaml
apiVersion: v1
kind: Service
metadata:
name: myapp-svc
spec:
ports:
- port: 80 # 서비스(프록시와 동일)를 찾아올 포트
targetPort: 8080 # 서비스와 연결할 대상
selector: # 서비스와 파드의 관계는 파드의 레이블과 서비스의 셀렉터로 이루어진다.
app: myapp-rs
# 서비스 생성
kubetl create -f myapp-svc.yaml
# 서비스 생성된 것 확인, IP 자동생성
kubectl get services
kubectl get pods
# 3번 보내면 로드밸런싱되어 각기 다른 파드에 접속하는 것을 확인할 수 있다.
프록시의 IP를 80번으로 요청, 프록시와 연결된 3개의 파드에 라우팅함
curl http://10.233.43.109
kubectl get endpoints (ep)
service discovery: 하나의 서비스가 다른 서비스를 찾는 것을 의미한다. 프론트엔드가 백엔드를 찾는다. 백엔드가 DB를 찾는다. 프록시가 프론트엔드를 찾는 등의 서비스를 찾는데 도움을 주는 매케니즘을 의미한다.
# 파드를 임시로 띄움, 바로 삭제되도록 --rm 옵션 추가
client 파드를 하나 띄었다고 생각
kubectl run -it nettool --image ghcr.io/c1t1d0s7/network-multitool --rm
서비스는 실행중
# host 질의, IP주소를 반환
host myapp-svc
# 서비스의 IP로 접속확인
curl 본인의 서비스의 IP주소
# 서비스의 이름으로 접속확인
curl http://myapp-svc
# 이름으로 접속하는 방법 풀네임:
이름 네임스페이스 타입 기본세팅 도메인
curl http://myapp-svc.default.svc.cluster.local
애플리케이션 중 일부(예: 프론트엔드)는 서비스를 클러스터 밖에 위치한 외부 IP 주소에 노출하고 싶은 경우가 있을 것이다.
쿠버네티스 ServiceTypes는 원하는 서비스 종류를 지정할 수 있도록 해준다. 기본 값은 ClusterIP이다.
Type 값과 그 동작은 다음과 같다.
서비스를 클러스터-내부 IP에 노출시킨다. 이 값을 선택하면 클러스터 내에서만 서비스에 도달할 수 있다. 이것은 ServiceTypes의 기본 값이다.
고정 포트 (NodePort)로 각 노드의 IP에 서비스를 노출시킨다. NodePort 서비스가 라우팅되는 ClusterIP 서비스가 자동으로 생성된다. < NodeIP>:< NodePort>를 요청하여, 클러스터 외부에서 NodePort 서비스에 접속할 수 있다.
# 레블리카셋으로 복제본 3개를 가지고 있음
vi myapp-rs.yaml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: myapp-rs
spec:
replicas: 3
selector:
matchLabels:
app: myapp-rs
template:
metadata:
labels:
app: myapp-rs
spec:
containers:
- name: myapp
image: ghcr.io/c1t1d0s7/go-myweb:alpine
ports:
- containerPort: 8080
kubectl create -f myapp-rs.yaml
# nodeport 생성
vi myapp-svc-np.yaml
apiVersion: v1
kind: Service
metadata:
name: myapp-svc-np
spec:
type: NodePort
ports:
- port: 80
targetPort: 8080
nodePort: 31111
selector:
app: myapp-rs
kubectl create -f myapp-svc-np.yaml
# 기존의 부분과 포트 부분이 다르다.
kubectl get svc
# 외부에서 접속 확인
kubectl get nodes -o wide
curl http://192.168.56.11:31111
curl http://192.168.56.21:31111
curl http://192.168.56.22:31111
curl http://192.168.56.23:31111
노드의 지정한 포트인 31111로 접속하게 되면 파드까지 연결이 된다. 이를 윈도우에서 작업을 하게될 때 이는 가상컴퓨터를 벗어난 클러스터 외부에서 접근을 하는 것인데, 기존의 clusterIP 타입은 클러스터 내부에서만 접근이 가능했고 nodePort나 Loadbalancer는 외부용 타입이다.
edit 명령으로 nodePort의 범위를 수정하려고 할때 포트의 범위는 30000-32767로 설정해야 한다.
nodeport 타입이지만 내부에서도 접속이 가능하다. 즉 내부로는 clusterIP의 역할도 하면서 외부로도 통신이 가능하다는 것이다.
클라우드 공급자의 로드 밸런서를 사용하여 서비스를 외부에 노출시킨다. 외부 로드 밸런서가 라우팅되는 NodePort와 ClusterIP 서비스가 자동으로 생성된다.
kubectl delete -f myapp-svc-np.yaml
vi myapp-svc-lb.yaml
apiVersion: v1
kind: Service
metadata:
name: myapp-svc-lb
spec:
type: LoadBalancer
ports:
- port: 80
targetPort: 8080
selector:
app: myapp-rs
kubectl create -f myapp-svc-lb.yaml
# 서비스 확인, 외부 IP 생성됨
kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.233.0.1 <none> 443/TCP 3d20h
myapp-svc-lb LoadBalancer 10.233.18.23 192.168.56.200 80:30290/TCP 47s
# 여러번 curl를 보내면 로드밸런스 되는 것을 확인 할 수 있다.
curl 192.168.56.200
https://openelb.io/
https://github.com/openelb/openelb
값과 함께 CNAME 레코드를 리턴하여, 서비스를 externalName 필드의 콘텐츠 (예:example.com)에 매핑한다. 어떤 종류의 프록시도 설정되어 있지 않다.
cat myapp-svc-extname.yaml
apiVersion: v1
kind: Service
metadata:
name: example
spec:
type: ExternalName
externalName: example.com
kubectl create -f myapp-svc-extname.yaml
kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
example ExternalName <none> example.com <none> 5s
kubernetes ClusterIP 10.233.0.1 <none> 443/TCP 3d21h
# client 파드를 하나를 임시로 띄움
kubectl run -it nettool --image ghcr.io/c1t1d0s7/network-multitool --rm
서비스는 실행중
# host에 질의해보면 example.com 의 실제 IP를 알려줌
host example
때때로 로드-밸런싱과 단일 서비스 IP는 필요치 않다. 이 경우, "헤드리스" 서비스라는 것을 만들 수 있는데, 명시적으로 클러스터 IP (.spec.clusterIP)에 "None"을 지정한다.
쿠버네티스의 구현에 묶이지 않고, 헤드리스 서비스를 사용하여 다른 서비스 디스커버리 메커니즘과 인터페이스할 수 있다.
kubespary 디렉터리에 들어가서
vi invetory/mycluster/group_vars/k8s_cluster/k8s-cluster.yml
# 서비스를 생성할 때, 클러스터 IP를 None으로 해주면 된다.
apiVersion: v1
kind: Service
metadata:
name: myapp-svc-headless
spec:
clusterIP: None
ports:
- port: 80
targetPort: 8080
selector:
app: myapp-rs-headless
인그레스 리소스가 작동하려면, 클러스터는 실행 중인 인그레스 컨트롤러가 반드시 필요하다.
즉 인그레스 자체는 이런 규칙들을 정의해둔 자원이고 실제로 동작시키는 것은 인그레스 컨트롤러이다.
kube-controller-manager 바이너리의 일부로 실행되는 컨트롤러의 다른 타입과 달리 인그레스 컨트롤러는 클러스터와 함께 자동으로 실행되지 않는다. 클러스터에 가장 적합한 인그레스 컨트롤러 구현을 선택하는데 이 페이지를 사용한다.
인그레스는 주로 클러스터 외부에서 안에 있는 파드에 접근할 때 사용하는 방법이다. 서비스와의 차이점은 주로 L7 영역의 통신을 담당해서 처리한다는 점이다.
즉 클러스터 내의 서비스에 대한 외부 접근을 관리하는 API 오브젝트이며, 일반적으로 HTTP를 관리한다.
인그레스는 외부에서 서비스로 접속이 가능한 URL, 로드 밸런스 트래픽, SSL / TLS 종료 그리고 이름-기반의 가상 호스팅을 제공하도록 구성할 수 있다.
인그레스 컨트롤러는 일반적으로 로드 밸런서를 사용해서 인그레스를 수행할 책임이 있으며, 트래픽을 처리하는데 도움이 되도록 에지 라우터 또는 추가 프런트 엔드를 구성할 수도 있다.
kubectl api-resources | grep ingresses
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: simple-fanout-example
spec:
rules:
- host: foo.bar.com
http:
paths:
- path: /foo
pathType: Prefix
backend:
service:
name: service1
port:
number: 4200
- path: /bar
pathType: Prefix
backend:
service:
name: service2
port:
number: 8080
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: name-virtual-host-ingress
spec:
rules:
- host: foo.bar.com
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: service1
port:
number: 80
- host: bar.foo.com
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: service2
port:
number: 80
인그레스의 각 경로에는 해당 경로 유형이 있어야 한다. 명시적 pathType 을 포함하지 않는 경로는 유효성 검사에 실패한다. 지원되는 경로 유형은 세 가지이다.
ImplementationSpecific: 이 경로 유형의 일치 여부는 IngressClass에 따라 달라진다. 이를 구현할 때 별도 pathType 으로 처리하거나, Prefix 또는 Exact 경로 유형과 같이 동일하게 처리할 수 있다.
Exact: URL 경로의 대소문자를 엄격하게 일치시킨다.
Prefix: URL 경로의 접두사를 / 를 기준으로 분리한 값과 일치시킨다. 일치는 대소문자를 구분하고, 요소별로 경로 요소에 대해 수행한다. 모든 p 가 요청 경로의 요소별 접두사가 p 인 경우 요청은 p 경로에 일치한다.
다중 일치
경우에 따라 인그레스의 여러 경로가 요청과 일치할 수 있다. 이 경우 가장 긴 일치하는 경로가 우선하게 된다. 두 개의 경로가 여전히 동일하게 일치하는 경우 접두사(prefix) 경로 유형보다 정확한(exact) 경로 유형을 가진 경로가 사용 된다.
호스트는 정확한 일치(예: "foo.bar.com") 또는 와일드카드(예: "* .foo.com")일 수 있다. 정확한 일치를 위해서는 HTTP host 헤더가 host 필드와 일치해야 한다. 와일드카드 일치를 위해서는 HTTP host 헤더가 와일드카드 규칙의 접미사와 동일해야 한다.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-wildcard-host
spec:
rules:
- host: "*.bar.com"
http:
paths:
- pathType: Prefix
path: "/bar"
backend:
service:
name: service1
port:
number: 80
- host: "foo.bar.com" # 이 경우 위 정책이 더 큰 범위이기 떄문에 서비스2는 절대 실행되지 않는다.
http:
paths:
- pathType: Prefix
path: "/foo"
backend:
service:
name: service2
port:
number: 80
레플리카셋, 노드포트 서비스, 인그레스 용 파드를 만든다.
# 인그레스용 파드 내용
vi myapp-ing.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp-ing
spec:
defaultBackend: # 밑에 룰에 의해서 라우팅 되지 않았을 떄 기본으로 라우팅되는 서비스를 의미
service:
name: myapp-svc-np
port:
number: 80
# 라우팅 규칙을 의미, path: 도메인 이름 뒤에 나오는 경로(예시. www.exple.com/testpath)
# backend: 인그레스 리소스가 보고있는 백엔드인 서비스를 의미
rules:
- host: www.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp-svc-np
port:
number: 80
# 레플리카셋, 노드포트 서비스는 위에서 했던 것과 동일
kubectl create -f myapp-rs.yaml
kubectl create -f myapp-svc-np.yaml
kubectl create -f myapp-ing.yaml
# ingress정보 보기
kubectl get ing
# rules를 확인해보면 호스트와 경로, 백엔드를 확인가능
kubectl describe ing myapp-ing
# ingress-nginx에 있는 파드들 확인
kubectl get po -n ingress-nginx -o wide
# 호스트를 설정했기 때문에 인그레스 룰 정책에 매칭되는것이 없어서 404 Not Found가 나타남
# -v 옵션으로 http요청과 응답을 확인가능(>: 요청, <: 응답)
curl 192.168.56.21
# 편법으로 도메인 이름으로 접속하는 방법
curl --resolve www.example.com:80:192.168.56.21 http:www.example.com
# /etc/hosts에 등록하는 방법, 추가
sudo vi /etc/hosts
192.168.56.21 www.example.com
# 내부에서 접속
curl http://www.example.com
컨테이너 내의 디스크에 있는 파일은 임시적이며, 컨테이너에서 실행될 때 애플리케이션에 적지 않은 몇 가지 문제가 발생한다.
한 가지 문제는 컨테이너가 크래시될 때 파일이 손실된다는 것이다. kubelet은 컨테이너를 다시 시작하지만 초기화된 상태이다.
두 번째 문제는 Pod에서 같이 실행되는 컨테이너간에 파일을 공유할 때 발생한다. 쿠버네티스 볼륨 추상화는 이러한 문제를 모두 해결한다. 파드에 대해 익숙해지는 것을 추천한다.
kubectl explain pod.spec.volumes
emptyDir 볼륨은 파드가 노드에 할당될 때 처음 생성되며, 해당 노드에서 파드가 실행되는 동안에만 존재한다. 이름에서 알 수 있듯이 emptyDir 볼륨은 처음에는 비어있다.
파드 내 모든 컨테이너는 emptyDir 볼륨에서 동일한 파일을 읽고 쓸 수 있지만, 해당 볼륨은 각각의 컨테이너에서 동일하거나 다른 경로에 마운트될 수 있다. 어떤 이유로든 노드에서 파드가 제거되면 emptyDir 의 데이터가 영구적으로 삭제된다.
즉, 동일한 내용의 파일을 공유해야하는 경우에 사용하는 것이고 예외적으로 파드의 라이프사이클과 함께가는 볼륨이다.
emptyDir 의 일부 용도
환경에 따라, emptyDir 볼륨은 디스크, SSD 또는 네트워크 스토리지와 같이 노드를 지원하는 모든 매체에 저장된다.
그러나, emptyDir.medium 필드를 "Memory"로 설정하면, 쿠버네티스에 tmpfs(RAM 기반 파일시스템)를 마운트하도록 할 수 있다.
tmpfs는 디스크와 다르게 매우 빠르다. 하지만 디스크와 다르게 노드 재부팅시 tmpfs가 지워지고, 작성하는 모든 파일이 컨테이너 메모리 제한에 포함된다.
# 스토리지 마운트
vi myapp-rs-emptydir.yaml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: myapp-rs-fortune
spec:
replicas: 1
selector:
matchLabels:
app: myapp-rs-fortune
template:
metadata:
labels:
app: myapp-rs-fortune
spec: # 파드의 스펙
containers:
- name: web-server
image: nginx:alpine
volumeMounts: # 컨테이너와 볼륨을 마운트 시키는 부분, 아래쪽 이름과 매칭됨
- name: web-fortune
mountPath: /usr/share/nginx/html # nginx의 문서경로
readOnly: true
ports:
- containerPort: 80
- name: html-generator
image: ghcr.io/c1t1d0s7/fortune
volumeMounts:
- name: web-fortune
mountPath: /var/htdocs # http의 문서경로
volumes: # 위 컨테이너와 동일한 들여쓰기, 어떤 스토리지를 사용할지와 이름을 정해줘야함
- name: web-fortune
emptyDir: {} # {}의 의미: default값을 사용한다는 의미, medium은 local disk, 사이즈는 제한없음
-> 똑같은 볼륨을 서로 다른 위치에 마운트 하고있다. 즉 2개의 컨테이너가 같은 볼륨을 바라보고 있다는 것이다.
# 로드 밸런스 서비스 생성
vi myapp-svc-emptydir.yaml
apiVersion: v1
kind: Service
metadata:
name: myapp-svc-fortune
spec:
type: LoadBalancer
ports:
- port: 80
targetPort: 80
selector:
app: myapp-rs-fortune
# 서비스와 emptyDir 파드 생성
kubrctl create -f myapp-rs-emptydir.yaml
kubectl create -f myapp-svc-emptydir.yaml
kubectl get rs,po,svc,ep
# 3초마다 새로운 내용을 갱신된다. 이는 fortune의 역할
curl 192.168.56.200
# 파드의 내용 확인해보기, volume의 내용!
kubectl describe myapp-svc-fortune
# 컨테이너에 접속
kubectl exec myapp-rs-fortune-w9wz2 --sh
-> 파드에 컨테이너가 2개기 때문에 오류가 난다.
# 각 컨테이너의 로그 확인, -c옵션 사용
kubectl logs myapp-rs-fortune-w9wz2 -c web-server
kubectl logs myapp-rs-fortune-w9wz2 -c html-generator
# html-generator의 쉘 실행
kubectl exec myapp-rs-fortune -it -c html-generator --sh
cd /var/htdocs
# 시간이 지날때마다 내용이 변경되는 것을 확인
cat index.html
# 쉘 스크립트로 내용이 변경되도록 작성해 둔 파일 확인, 이로인해 내용이 변경되는 것이다.
cd /bin
cat fortune.sh
# web-server의 쉘 실행
kubectl exec myapp-rs-fortune -it -c web-server --sh
cd /usr/share/nginx/html
# 이 파일이 스토리지에 의해 연결된 것
cat index.html
지금은 사용할 수 있지만 나중에 사라진다고 한다. git repo가 있는 컨테이너를 프로비전 하려면 초기화 컨테이너(InitContainer)에 EmptyDir을 마운트하고, 여기에 git을 사용해서 repo를 복제하고, EmptyDir을 파드 컨테이너에 마운트 한다.
vi myapp-pod-git.yaml
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod-git
spec:
initContainers: # 초기화 컨테이너
- name: git-clone
image: alpine/git # git 이미지
args: # CMD 배치: git clone ... 명령어를 의미
- clone
- --single-branch
- --
- https://github.com/kubernetes/kubernetes # 쿠버네티스 소스코드
- /repo
volumeMounts:
- name: git-repository
mountPath: /repo
containers:
- name: git-container
image: busybox # 2MB짜리 작은 리눅스의 일종, 이는 쉘을 실행한다.
args: ['tail', '-f', '/dev/null'] # 아무것도 없는 장치(/dev/null)를 tail -f를 하면 영구적으로 프로세스를 살아있게 만든다.
# 이는 컨테이너를 안죽게 만들기 위함이다.
volumeMounts:
- name: git-repository
mountPath: /repo
volumes:
- name: git-repository
emptyDir: {}
2개의 창을 사용하여 초기화 컨테이너가 종료된 후 메인 컨테이너가 실행되는 것을 확인
# 실시간으로 상황 확인
watch -n1 -d kubectl get pods
# 파드 생성
kubectl create -f myapp-pod-git.yaml
# 컨테이너가 생성된 이후 접속하여 /repo 디렉터리에 소스코드가 다운되어있는 것을 확인
kubectl exec myapp-pod-git -it -c git-container --sh
cd /repo
init 컨테이너가 먼저 볼륨에 정보를 채워넣고 종료된다. 그후 메인 컨테이너가 실행되고 정보가 채워져있는 볼륨을 사용하는 것이다.
alpine: 비즈박스로 만든 리눅스의 일종, 패기지 관리자가 존재하지 않는다.(yum, apt 등) 따라서 모든걸 다 컴파인 해줘야한다. 일반 것과 동일하게 작동하는데 용량이 적다는 장점이 있다.
containers: Main 어플리케이션
initContainers: 보조 어플리케이션으로 종료가 무조건 되어야한다. 즉 메인 컨테이너가 시작되기전에 초기화 작업을 하기위해 실행된다고 보면 된다. 종료를 보장해야되기 때문에 프로브를 설정할 수 없다.