kubernetes 시작하기
모든 리소스는 오브젝트 형태로 관리된다.
컨테이너의 집합 = Pods
집합을 관리하는 컨트롤러 = Replica Set
사용자 = Service Acoount
노드 = Node
하나의 오브젝트로 사용할 수 있음
# 사용할 수 있는 오브젝트 확인
kubectl api-resources
# 특정 오브젝트의 간단한 설명
kubectl explian 오브젝트_이름
전부다 다루지는 않고 자주 사용하는 것 위주로 설명하겠다.
쿠버네티스는 명령어로도 사용할 수 있지만, YAML 파일을 더 많이 사용한다.
컨테이너, 설정값, 비밀값등 모든 것을 YAML 파일로 작성하기 때문에 쿠버네티스를 잘 사용한다는 것은 YAML 파일을 잘 작성하는 것이다.
쿠버네티스는 여러 개의 컴포넌드로 구성돼 있다.
크게 마스터 노드와 워커 노드로 나눠어져 있다. (관리와 애플리케이션 컨테이너)
모든 노드 - 프락시, 네트워크 플로그인, kubelet(클러스터 구성)
마스터 노드 - API서버, 컨트롤러 매니저, 스케줄러, DNS 서버
워커 노드 - 애플리케이션
포드(Pod) : 컨테이너를 다루는 기본 단위
쿠버네티스의 기본 단위로 포드는 1개 이상의 컨테이너로 구성된 컨테이너의 집합 (스웜에서는 서비스 개념)
nginx-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: my-nginx-pod
spec:
containers:
- name: my-nginx-container
image: nginx:latest
ports:
- containerPort: 80
protocol: TCP
일반적으로 쿠버네티스의 YAML파일
apiVersion: 오브젝트의 API버전
kind: 리소스의 종류
metadata: 라벨, 주석, 이름 등과 같은 리소스의 부가 정보를 입력
spec: 리소스를 생성하기 위한 자세한 정보를 입력
이렇게 구성된다
# 작성한 YAML 파일 쿠버네티스에 생성
kubectl apply -f nginx-pod.yaml
# 특정 오브젝트 확인
kubectl get <오브젝트 이름>
# 생성된 리소스의 자세한 정보
kubectl describe pods <포드이름>
하지만 아직 외부에서 접근할 수 있도록 노출된 상태는 아니다. 따라서 포드의 서버로 요청을 보내려면 포드 컨테이너 내부 IP로 접근해야한다.
위에 있는 명령어를 사용해보자
kubectl describe pods <이름>
-> IP : ~ 나올 것이다 . 192.168.180.100
확인 된 IP를 클러스터의 노드 중 하나에 접속한 후
curl 192.168.180.100 으로 켜보자.
# 포드 컨테이너 내부로 직접 들어가기
kubectl exec -it <my-nginx-pod bash>
# 포드 log 확인 명령어
kubectl logs <포드 이름>
오브젝트 삭제
kubectl delete -f nginx-pod.yaml
새로운 우분투 컨테이너 추가해보자
apiVersion: v1
kind: Pod
metadata:
name: my-nginx-pod
spec:
containers:
- name: my-nginx-container
image: nginx:latest
ports:
- containerPort: 80
protocol: TCP
- name: ubuntu-sidecar-container
image: alicek106/rr-test:curl
command: ["tail"]
args: ["-f", "/dev/null"] # 포드가 종료되지 않도록 유지합니다
대시를 사용하는 항목은 여러 개의 항목을 정의할 수 있음을 의미한다.
여기서 spec.containers의 하위 항목은 -name: 가 같이 대시로 구분이 되며, 여러 개의 컨테이너가 정의된 것이다.
# -c옵션으로 어떤 컨테이너에 대해 명령어를 수행할지 명시할 수 있다. 밑에는 배쉬 셸 실행
kubectl exec -it my-nginx-pod -c ubuntu-sidecar-container bash
이때 HTTP 요청을 전송하면 서버의 응답이 도착하는 것을 확인할 수 있다.
우부투 컨테이너가 서버를 실행하고 있지 않아도 우분투 컨테이너 로컬 호스트에서 Nginx 로 접근이 가능하다.
이는 포드 내의 컨테이너들이 네트워크 네임스페이스 등과 같은 리눅스 네임스페이스를 공유해 사용하기 때문
포드가 공유하는 리눅스 네임스페이스에 네트워크 환경만 있는 것은 아니다. 1개의 포드에 포함된 컨테이너들은 여러 개의 리눅스 네임스페이스를 공유한다. 그러나 당장은 자세히 알 필요 없으니 넘어 가겠다.
'하나의 포드는 하나의 완전한 애플리케이션'
하지만 부가적인 기능이 필요할때는 주 컨테이너를 정하고, 기능 확장을 위한 추가 컨테이너를 함께 포드에 포함시킬 수 있다. 이렇게 정의된 부가적인 컨테이너를 사이드가 컨테이너라고 부르며, 사이드카 컨테이너는 포드 내의 다른 컨테이너와 네트워크 환경 등을 공유하게 된다.
레플리카셋 (Replica Set) : 일정 개수의 포드를 유지하는 컨트롤러
사용 이유 : 마이크로 서비스에서는 여러 개의 동일한 포드를 생성한 뒤 외부 요청이 각 포드에 적절히 분배 될 수 있어야 함. 이 때 동일한 여러 개의 포드를 생성하는 방법
1. 다른 이름을 가지는 여러 개의 포드를 직접 만듬 : 적절하지 못함
- 일일이 정의하는 것은 매우 비효율적
- 어떠한 이유로든지 삭제되거나, 포드가 위치한 노드에 장애가 발생해 더 이상 포드에 접근하지 못할 때 직접 포드를 삭제하고 다시 생성하지 않는 한 해당 포드는 다시 복귀되지 않음
2. 리플리카셋(쿠버네티스 오브젝트)를 이용하는 것
- 정해진 수의 동일한 포드가 항상 실행되도록 관리
- 노드 장애 등 이유로 포드를 사용할 수 없다면 다른 노드에서 포드를 다시 생성
사용하기
replicaset-nginx.yaml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: replicaset-nginx
spec:
replicas: 3 # 여기서부터
selector:
matchLabels:
app: my-nginx-pods-label # 리플리카셋 정의
template:
metadata: # 포드 정의
name: my-nginx-pod
labels:
app: my-nginx-pods-label
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
spec.replicas: 동일한 포드를 몇개 유지시킬 것인지 설정
spec.template ~: 포드를 생성할 때 사용할 템플릿을 정의, 즉 포드를 사용했던 내용을 동일하게 리플리카셋에도 정의
# 레플리카셋을 직접 생성해보자
kubectl apply -f replicaset-nginx.yaml
명령어를 사용할때 pods 대신 po로 replicasets 대신 rs로 사용할 수 있다.
# 삭제 또한 동일하다
kubectl delete -f
# 또는
kubectl delete rs
# 명령어를 사용하면 된다.
동작 원리
레플리카셋은 포드와 연결돼 있지 않다. 오히려 느슨한 연결을 유지하고 있으며, 이러한 느슨한 연결은 포드와 레플리카셋 정의 중 라벨 셀렉터를 이용해 이루어진다.
라벨은 쿠버네티스 리소스의 부가적인 정보를 표현할 수 있을 뿐만 아니라, 서로 다른 오브젝트가 서로를 찾아야 할 때 사용되기도 한다.
ex)레플리카셋은 spec.selector.matchLabel 에 정의된 라벨을 통해 생성해야 하는 포드를 찾는다.
즉, app:my-nginx-pods-label 라벨을 가지는 포드의 개수가 replicas 항목에 정의된 숫자인 3개와 일치하지 않으면 포드를 정의하는 포드 템플릿 항목의 내용으로 포드를 생성하는 것이다.
이 말은 내가 수동으로 app:my-nginx-pods-label 라벨을 가지는 포드를 1개 만들었을 경우 2개만 만든다는 이야기다.
또한 생성된 것중 하나의 포드 라벨을 삭제한다면 라벨을 가지는 포드가 2개가 됐으므로, 하나를 더 생성한다.
그래서 레플리카셋과 포드의 라벨은 고유한 키-값 쌍이어야만 하는 것이다. 라벨을 레플리카셋에서 중요한 비중을 차지한다. 명심하자.
이전 버전의 쿠버네티스에서는 레플리카셋이 아닌 레플리케시션 컨트롤러라는 오브젝트를 통해 포드의 개수를 유지했었다. 하지만 더 이상 사용하지 않으므로 설명하지 않겠다.
디플로이먼트(Deploymetn) : 레플리카셋, 포드의 배포를 관리
레플리카셋만으로는 마이크로 서비스 구조의 컨테이너를 구성할 수 없다. 대부분은 리플리카셋과 포드의 정보를 정의하는 디폴로이먼트롤 YAML파일에 정의해 사용한다.
디플로이먼트는 레플리카셋의 상위 오브젝트이다. 따라서 디플로이먼트를 사용하면 포드와 레플리카셋을 직접 생성할 필요가 없다.
deployment-nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx-deployment
spec:
replicas: 3
selector:
matchLabels:
app: my-nginx
template:
metadata:
name: my-nginx-pod
labels:
app: my-nginx
spec:
containers:
- name: nginx
image: nginx:1.10
ports:
- containerPort: 80
kind 항목이 Deployment로 바뀌었을 뿐 기존의 YAML과 달라진 것이 거의 없다.
# Deployment 생성
kubectl apply -f deployment-nginx.yaml
# Deployment 출력
kubectl get deployment
# Deployment 삭제
kubectl delete deploy my-nginx-deployment
디플로이먼트로부터 생성된 레플리카셋과 포드는 특이한 해시값을 포함한 이름으로 생성됐다. 이 해시값은 포드를 정의하는 템플릿으로부터 생성된 것으로, 이 해시값에 대해서는 뒤에서 다시 설명할 '디플로이먼트를 사용하는 이유'에서 다루겠다.
디플로이먼트를 사용하는 이유
[핵심 이유]:
애플리케이션의 업데이트와 배포를 더욱 편하게 만들기 위해서
- 애플리케이션을 업데이트할 때 레플리카셋의 변경 사항을 저장하는 리버전을 남겨 롤백을 가능하게 해주고 무중단 서비스를 위해 포드의 롤링 업데이트의 전략을 지정할 수도 있다.
# 생성할때 뒤에 record를 붙여보자
kubectl apply -f deployment-nginx.yaml --record
그 후 포드의 이미지 버전을 nginx:1.11으로 변경하려면 다음과 같은 명령어를 입력하면 된다.
kubectl set image my-nginx-deployment nginx=nginx:1.11 --record
그 후 레플리카셋의 목록을 출력해보면 이상하게 두 개의 레플리카셋이 있을 것이다. 즉 이전 버전의 리플리카셋을 삭제하지 않고 남겨두고 있는 것이다.
--record=true : 변경 사항을 기록함으로 해당 버전의 이플리카셋을 보존
--to-revision=[리버전 번호] : 리버전의 번호로 롤백함
지금 어떠한 상태인지 알고 싶다면
1. kubectl get replicasets에 있는 DESIRED CURRENT READT를 보면 된다.
2. kubectl describe deploy [라벨 이름]
서비스(Service): 포드를 연결하고 외부에 노출
디플로이먼트를 통해 생성된 포드에 어떻게 접근할 수 있을지에 대해서 아직 알아보지 않았다.
도커 컨테이너와 마찬가지로 포드의 IP는 영속적이지 않아 항상 변할 수 있단느 점도 유의해야 한다.
쿠버네티스에서는 포드에 접근하도록 정의하는 방법이 도커와 다르다. 디플로이먼트를 생성할 때 포드를 외부에 노출하지 않으며, 디플로이먼트의 YAML 파일에는 단지 포드의 애플리케이션이 사용할 내부 포트만 정의한다.
YAML파일에서 containerPosr를 정의했다고 해서 이 포드가 바로 외부로 노출되는 것은 아니다. 이 때 별도로 서비스라고 부르는 쿠버네티스 오브젝트를 생성해야 한다.
서비스에는 다양한 기능이 있지만 핵심 기능만 나열해보자면 아래와 같다.
- 여러 개의 포드에서 쉽게 접근할 수 있도록 고유한 도메인 이름을 부여한다.
- 여러 개의 포드에 접근할 때, 요청을 분산하는 로드 밸런서 기능을 수행한다.
- 클라우드 플랫폼의 로드 밸런서, 클러스터 노드의 포트 등을 통해 포드를 외부로 노출한다.
서비스의 종류
앞서 서비스의 개념과 사용 방법을 익히기 위해 포드와 서비스를 연결해보겠다.
deployment-hostname.yamldeployment-hostname.yaml
deployment-hostname.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: hostname-deployment
spec:
replicas: 3
selector:
matchLabels:
app: webserver
template:
metadata:
name: my-webserver
labels:
app: webserver
spec:
containers:
- name: my-webserver
image: alicek106/rr-test:echo-hostname
ports:
- containerPort: 80
kubectl run -i --tty --rm debug --image=alicek106/ubuntu:curl --restart=Never curl 192.168.203.138 | grep Hello
포드에 접근할 수 있는 규칙을 정의하는 서비스 리소스를 새롭게 생성해 보자, 그 전에 한가지 알아야 할 점은 쿠버네티스의 서비스는 포드에 어떻게 접근할 것이냐에 따라 종류가 여러개로 세분화 돼있다.
우리의 목적에 맞는 적절한 서비스를 선택해야한다.
- ClusterIP 타입: 쿠버네티스 내부에서만 포드들에 접근할 때 사용
- NodePort 타입: 포드에 접근할 수 있는 포트를 클러스터의 모든 노드에 동일하게 개방
- LoadBalancer 타입: 클라우드 플랫폼에서 제공하는 로드 밸런서를 동적으로 프로비저닝해 포드에 연결
ClusterIP 타입
apiVersion: v1
kind: Service
metadata:
name: hostname-svc-clusterip
spec:
ports:
- name: web-port
port: 8080
targetPort: 80
selector:
app: webserver
type: ClusterIP
spec.selector : selector 항목은 이 서비스에서 어떠한 라벨을 가지는 포드에 접근할 수 있게 만들 것인지 결정 위에는 webserver이라는 라벨을 가지는 포드들의 집합에 접근할 수 있는 서비스를 생성
spec.ports.port : 생성된 서비스는 쿠버네티스 내부에서만 사용할 수 있는 고유한 IP를 할당받음
spec.ports.targetPort : selector 항목에서 정의한 라벨에 의해 접근 대상이 된 포드들이 내부적을 사용하고 있는 포트를 입력함
spec.type : 이 서비스가 어떤 타입인지 나타냄
# 서비스 생성
kubectl apply -f hostname-svc-clusterip.yaml
# 생성된 서비스 목록 확인
kubectl get services
# services 대신 svc 가능
# 서비스 삭제
kubectl delete svc 이름
서비스의 IP와 포트를 통해 포드에 접근할 수 있다. 게다가 서비스와 연결된 여러 개의 포드에 자동으로 요청이 분산된다. 즉 알아서 로드 밸런싱을 수행하는 것이다.
사비스에는 IP뿐만 아니라 서비스 이름 그 자체로도 접근 할 수 있다. 서비스나 포드를 쉽게 찾을 수 있도록 내부 DNS를 구동하고 있으며, 포드들은 자동으로 이 DNS를 사용하도록 설정되기 때문이다.
NodePort 타입
hostname-svc-codeport.yaml
apiVersion: v1
kind: Service
metadata:
name: hostname-svc-nodeport
spec:
ports:
- name: web-port
port: 8080
targetPort: 80
selector:
app: webserver
type: NodePort
생성은 위와 동일
서비스 출력 화면을 보면 클러스터의 모든 노드에 내부 IP 또는 외부 IP를 통해 31136 포트로 접근하면 동일한 서비스에 연결할 수 있다. (31136포트를 통해 모든 노드에서 동일하게 접근)
단 AWS, GKE에서 쿠버네티스를 쓰고 있다면 방화벽 설정을 추가해야한다.
# 규칙 추가
gcloud compute firewall-rules create temp-nodeport-svc --allow=tcp:포트번호
# 규칙 제거
gcloud compute firewall-rules delete temp-nodeport-svc
- 포트범위를 직접 지정하려면 API 서버의 다음의 옵션을 추가하거나 수정한다.
--service-node-port-range=30000-35000
NodePort 타입의 서비스는 기본적으로 ClusterIP 기능을 포함하고 있다. 그렇기에 서비스의 내부 IP와 DNS 이름을 사용해 접근할 수 있다.
LoadBalancer 타입
이 서비스는 서비스 생성과 동시에 로드 밸런서를 새롭게 생성해 포드와 연결한다.
NodePort를 사용할 때는 각 노드의 IP를 알아야만 포드에 접근할 수 있었지만, LoadBalancer 타입의 서비스는 클라우드 플랫폼으로부터 도메인의 이름과 IP를 항당받기 때문에 더 쉽게 포드에 접근할 수 있다.
단, 이 서비스는 로드 밸런서를 동적으로 생성하는 기능을 제공하는 환경에서만 사용할 수 있다는 점을 알아두자.
AWS, GCP 등과 같은 클라우드 플랫폼 환경에서만 사용 가능하다.
hostname-svc-lb.yaml
apiVersion: v1
kind: Service
metadata:
name: hostname-svc-lb
spec:
ports:
- name: web-port
port: 80
targetPort: 80
selector:
app: webserver
type: LoadBalancer
kubernetes.io/ko/docs/tutorials/stateless-application/expose-external-ip-address/
만약
pending 이라고 뜬다면 플랫폼 API를 잘 못 쓰고 있는 것이라고 생각된다. ( 내가 그랬다. )
추가로 돈을 내고 싶지는 않아서 진행하지 않았다.