프로그래머스 데브코스 82일차 with. TS 웹 풀스택
📚요약
지난 시간에는 도커에 대해 공부하고 몇 가지 실습을 진행했습니다. 이번 시간에는 쿠버네티스에 대해 알아보겠습니다.
📖쿠버네티스(Kubernetes)
🍯tip! 쿠버네티스는 이름이 길기 때문에 k8s로 줄여서 사용하기도 합니다.
📄쿠버네티스 클러스터 구성 요소
- 마스터 노드(컨트롤 플레인)
- kubectl(필수는 아니다)
- API 서버, etcd: 클러스터의 구성 요소 중 중심 역할
- 컨트롤러 매니저
- 스케줄러
- 워커 노드
- 컨테이너 런타임(CRI) : 포드를 이루는 컨테이너를 실행한다.
- kubelet: 포드의 구성 내용을 받아 CRI에 전달하고 컨테이너의 동작 상태를 모니터링
📄기능
- 컨테이너 밸런싱 : 포드의 부하 균등화 수행
- 트래픽 로드 밸런싱 : 복제본이 둘 이상인 경우 트래픽 부하 균등화를 수행해 클러스터 내부에 적절히 분배
- 동적 수평 스케일링 : 인스턴스 수를 동적으로 늘리거나 줄여 자원을 효율적으로 활용
- 오류 복구 : 포드와 노드를 지속적으로 모니터링해 장애 발생 시 새 포드를 실행해 지정된 복제본의 수 유지
- 롤링 업데이트 : 지연 시간을 적용해 순차적으로 업데이트 배포
- 스토리지 오케스트레이션 : 다양한 스토리지 시스템(AWS EBS, Google CGE Persistent Disk)을 마운트 할 수 있음
- 서비스 디스커버리 : 수명이 짧은 포드의 동적 성질 관리를 위해 자체 DNS 기반으로 서비스를 동적 바인딩할 수 있는 기능 제공
📄포드(Pod)의 생명 주기
- 포드 생성 요청
- 포드 생성 및 API 서버에 상태 전달
- 적절한 워커 노드에 포드 실행 요청
- kubelet이 CRI에 요청해 포드가 사용 가능한 상태가 됨
k8s는 절차적 구조가 아닌 선언적 구조를 가지고 있어 추구하는 상태를 선언하면 현재 상태와 비교해 지속적으로 맞춰가려고 노력합니다.
📄k8s 오브젝트
- Pod : 한 개 이상의 컨테이너로 단일된 목적의 일을 하기 위한 묶음의 단위. 독립적인 공간과 사용 가능한 IP를 가지고 있다. 언제든지 죽을 수 있음.
- Namespace : k8s 클러스터에서 사용되는 리소스들을 구분해 관리하는 그룹
- Volume : 포드가 생성될 때 포드에서 사용할 수 있는 디렉토리를 제공
- Service : 클러스터 내부의 포드에 의해 실행되는 앱을 외부에서 접근 가능하도록 노출하는 기능.
- ClusterIP: 클러스터 내부에서만 접근할 수 있는 IP 할당. 포트 포워딩 또는 프록시를 통해 클러스터 외부로부터 접근이 가능하지만 이는 테스트나 디버깅 목적으로 제한적으로 이용하는 경우이다.
- NodePort: 특정 포트를 열고 해당 포트로 들어오는 모든 요청을 노드포트 서비스에 전달한다.
- LoadBalancer: 클러스터 외부의 로드밸런서를 이용해 부하 균등화를 수행한다.
- Ingress: 엄밀히 말하면 서비스의 한 종류는 아니다. 복수의 서비스에 대해 목적에 따라 트래픽을 연결하는 도구이다.
- HPA(동적 수평 오토스케일링): 부하량에 따라 deployment의 포드 수를 유동적으로 관리하는 기능.
- 등
- Deployment : 기본 오브젝트를 효율적으로 작동하도록 조합하고 추가로 구현
🍯tip! 동일한 포드들의 복제본 모음을 레플리카셋(replicaset)이라고 합니다.
📄인프라 구축
로컬 단계에서는 kubeadm, docker desktop 등을 설치해 로컬에서 간단하게 개발이나 테스트 시 이용할 수 있습니다.
Public clouds
- AWS의 EKS
- GCP의 GKE
- MS의 AKS
On-prem
- SUSE의 Rancher
- RedHat의 OpenShift
📄쿠버네티스 기본 설정
docker-desktop에서 설정에 들어가면 Kubernetes 탭이 있고 해당 탭에서 Enable을 체크해 주면 됩니다. 그 후 정상적으로 동작하는 것을 확인하기 위해서는 `kubectl get nodes` 명령어를 통해 오류가 뜨지 않으면 정상 동작하는 것으로 확인할 수 있습니다.
📄볼륨
k8s는 클러스터 내에서 이용할 수 있는 저장장치의 추상화된 객체로 볼륨을 정의했습니다.
- PV(Persistant Volume): 클러스터 내에 존재하는 스토리지를 추상화한 것으로 클러스터 내에 존재하는 물리적 저장장치 이용 가능 및 다양한 원격 저장소 및 클라우드 서비스 이용 가능.
- PVC(Persistant Volume Claim): PV를 이용하기 위한 요청으로 저장 공간의 크기와 접근 모드를 지정. 포드 : 노드 = PVC : PV
📄실습
`kubectl get <타입>` 명령어로 타입들의 현재 상태를 확인할 수 있고 타입으로는 pods, nodes, deployment, svc가 있습니다. -o wide 옵션을 사용하면 더 자세한 내용을 알 수 있습니다. 각각의 자세한 내용을 알기 위해서는 `kubectl describe <'pod'|'deployment' 등> <이름>`으로 확인할 수 있습니다. 삭제는 `kubectl delete <타입> <이름>`을 사용하면 됩니다.
📑포드와 deployment 생성
`kubectl run <포드 이름> --image=<사용할 이미지>`, `kubectl create <타입> <이름> --image=<사용할 이미지>`를 통해 각각 포드와 deployment를 생성할 수 있습니다.
📑 레플리카셋 늘리기
이제 생성된 deployment의 레플리카셋을 늘리려면 `kubectl scale deployment <이름> --replicas=<갯수>` 명령어를 사용하면 됩니다.
📑NodePort 형태
`kubectl expose <타입> <노출시키는 이름> --type=<서비스 타입> --name=<서비스화 시킬 이름> -- port=<포트 번호>` 명령어를 통해 만든 것을 외부로 노출시킬 수 있습니다.
📑manifest를 이용해 오브젝트 생성
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
위 파일은 오브젝트를 생성하기 위한 yaml 파일입니다. 해당 파일을 만들고 `kubectl apply -f <파일 이름>`을 통해 설정해 둔 그대로 오브젝트를 생성할 수 있습니다.
📑Flask 응용
클러스터에서 수신한 요청에 대해 k8s가 어떻게 반응하는지에 대해 hostname과 IP로 모니터링을 하겠습니다. 시작하기에 앞서 4가지 파일의 준비가 필요합니다.
- app.py: Flask를 사용해 /요청에 대해 hostname과 ip 주소를 출력할 페이지를 응답 파일
- requirements.txt: Flask 응용 실행에 필요한 패키지를 지정하기 위한 파일
- site.conf: nginx 웹 서버를 이용해 flask 앱을 실행하기 위한 설정 파일. / URL에 대한 요청을 redirect
- start.sh: 컨테이너가 생성될 때 웹 서버와 flask 응용을 구동하기 위한 셸 스크립트
from flask import Flask
import socket
app = Flask(__name__)
hostname = socket.gethostname()
ipv4 = socket.gethostbyname(hostname)
message = f'<p>Hostname: {hostname}</p><p>IPv4 Address: {ipv4}</p>\n'
@app.route("/")
def root():
return message
△ app.py
Flask
△ requirements.txt
server {
listen:80;
server_name localhost;
access_log /var/log/nginx/access.log main;
location / {
proxy_pass http://127.0.0.1:5000;
}
}
△ site.conf
service nginx start
/flaskapp/venv/bin/flask --app app run --host 0.0.0.0
△ start.sh
위 파일들이 준비가 되었다면 해당 파일들을 이미지로 만들기 위해 Dockerfile도 만들어 보겠습니다.
FROM nginx:latest
RUN apt update
RUN apt install -y python3-full
RUN apt install -y procps
WORKDIR /flaskapp
RUN python3 -m venv /flaskapp/venv
COPY requirements.txt requirements.txt
RUN /flaskapp/venv/bin/pip install -r requirements.txt
COPY app.py app.py
COPY site.conf /etc/nginx/sites-available/flaskapp.conf
RUN ln -s /etc/nginx/sites-available/flaskapp.conf /etc/nginx/conf.d
RUN mv /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf.bak
COPY start.sh start.sh
RUN chmod 777 start.sh
ENTRYPOINT "/flaskapp/start.sh"
△ Dockerfile
빌드를 통해 빌드해 주고 docker hub에 올리는 것은 자유입니다. 만들어진 이미지를 가지고 yaml 파일을 사용해 deployment와 service를 만들어 보겠습니다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: dpy-hname
labels:
app: hname
spec:
replicas: 3
selector:
matchLabels:
app: hname
template:
metadata:
labels:
app: hname
spec:
containers:
- name: hname
image: nulzi/hname:latest
ports:
- containerPort: 80
△ deployment.yaml
apiVersion: v1
kind: Service
metadata:
name: svc-hname
spec:
type: NodePort
selector:
app: hname
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
nodePort: 30000
△ service.yaml
`kubectl apply -f <파일 이름>` 명령어를 통해 deployment와 service를 만들고, curl 명령어를 통해 접속하면 host name과 ip 주소가 레플리카셋을 번갈아가며 요청하는 것을 알 수 있습니다.
➕ 레플리카셋을 1로 줄이고 위 메시지가 자동으로 출력되는 사이에 포드가 삭제되면 어떻게 되는지 살펴보겠습니다.
$i=0
while(1)
{
% { $i++; write-host -NoNewLine "$i $_" }
(Invoke-RestMethod "http://localhost:30000")-replace '\n', " "
Start-Sleep -Seconds 1
}
자동으로 동작하기 위해서 check.ps1 파일을 만들어 줍니다. 파워쉘에서 동작시켜 보겠습니다.
🍯tip! 파워쉘에서 동작하는 과정에서 스크립트를 실행하지 못하는 에러가 발생할 수 있습니다. 이때 다음 글을 참고하면 좋을 것 같습니다.
결과를 보면 hostname과 ip 주소가 중간에 변경된 것을 알 수 있는데, 해당 포드가 삭제되었을 때 자동으로 새로운 포드가 생성되어 이어서 연결된 것을 확인할 수 있습니다.
➕ 컨테이너에 오류가 발생하면 어떻게 되는지 프로세스를 강제로 종료해 보겠습니다.
실행되고 있는 포드에 접속해서 프로세스를 강제로 종요해보겠습니다.
`kubectl exec -it <포드 이름> -- /bin/bash` 명령어를 통해 내부에 접속합니다.
`ps ax | grep flask` 명령어를 통해 실행 중인 프로세스 목록을 불러옵니다.
`kill -9 <프로세스 id>` 명령어를 통해 실행 중인 프로세스를 죽입니다. 그렇게 되면 자동으로 동작하고 있는 곳에서 잠시 에러가 발생합니다. 하지만 곧 복구되어 다시 정상적으로 동작되는 것을 볼 수 있습니다.
복구가 되었는데 아무것도 변화가 없을까요? 아닙니다. 포드를 살펴보면 재시작의 횟수가 늘어난 것을 볼 수 있습니다. 결국 포드는 오류가 발생하면 재시작에 의해 다시 정해진 동작을 보장하는 것을 알 수 있습니다.
📑포트의 업데이트와 복구
● --record 옵션 사용하기
우선 image의 버전을 1.16.0으로 지정해서 deployment를 생성합니다.
버전을 1.17.0으로 올려서 --record 옵션과 같이 다시 생성합니다.
rollout으로 살펴보면 업데이트가 되고 기록이 남은 것을 확인할 수 있습니다.
하지만 --record 옵션은 삭제될 예정이기 때문에 다른 것을 사용해 보겠습니다.
● annotate 사용하기
deployment를 생성하고 service.yaml 파일을 이용해 svc도 만들어 줍니다.
apiVersion: v1
kind: Service
metadata:
name: svc-nginx
spec:
type: NodePort
selector:
app: nginx
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
nodePort: 30000
△ service.yaml
`curl -I --silent localhost:<포트번호>` 명령어를 실행하면 현재 실행되는 정보를 얻을 수 있습니다.
현재는 1.16.0 버전인 것을 확인할 수 있습니다. 이제 버전을 업데이트해 주겠습니다. `kubectl set image deployment nginx-deployment nginx=nginx:1.17.0` 명령어를 사용합니다.
업데이트한 버전을 annotate를 이용해 기록을 남겨보겠습니다. `kubectl annotate deployment nginx-deployment kubernetes.io/change-cause="<메시지>"`
최종적으로 history를 다시 보면 잘 업데이트된 것을 확인할 수 있습니다.
➕ 업데이트를 실패하는 경우
의도적으로 잘못된 이미지의 버전으로 업데이트해서 오류가 발생하는 경우를 살펴보겠습니다. 새로운 버전으로 생성하려는 포드의 상태가 ImagePullBackOff 상태로 변하게 되고 업데이트가 진행되지 않습니다.
➕ 실패한 경우 되돌리기
위와 같이 오류가 발생해 실패하는 경우 `kubectl rollout undo deploy <포드 이름>` 명령어를 사용해 되돌릴 수 있습니다.
이때 history도 변했는지 살펴보겠습니다.
2번에 있던 과정으로 4번으로 옮겼습니다.
🍯tip! 타입에 적는 deployment를 deploy만 적어도 같은 방식으로 동작한다.
📑hpa 실습
원본 파일 : https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.7.1/components.yaml
수정 내역 : TLS 인증을 무시하도록 설정
수정한 파일을 kubectl apply를 이용해 등록합니다.
https://k8s.io/examples/application/php-apache.yaml로 제공되는 파일을 apply 시켜서 웹 서버를 배포하고 HPA를 사용해 cpu 사용율 50%를 기준으로 동적으로 스케일링시켜 보겠습니다.
`kubectl autoscale deploy <서비스 이름> --cpu-percent=50 --min=1 --max=10` 명령어를 사용하면 됩니다. 이제 강제로 부하를 주고 변화를 관찰해 보겠습니다.
`kubectl run -it load-gen --rm --image=busybox --restart=Never -- /bin/sh -c "while sleep 0.01; do wget -q -O- http://php-apache; done"` 명령어를 통해 부하를 줄 수 있습니다. 부하의 변경은 `kubectl get hpa php-apache --watch` 명령어를 통해 확인할 수 있습니다. 시간이 지나 부하가 커지면 포드의 수가 늘어나고, 부하를 줄이면 늘어났던 포드의 수가 다시 줄어드는 것을 볼 수 있습니다.
📑볼륨 실습
에러 발생으로 인해 실패했습니다. 추후에 다시 시도해서 수정하겠습니다.
❔▪❓
Q. manifest를 이용해 오브젝트를 생성할 때 kubectl apply와 create의 차이
A.create 명령을 실행하면 쿠버네티스 리소스를 생성합니다. 다만 이미 같은 이름의 자원이 배포되어 있으면 오류를 출력합니다. apply 명령은 실행하면 쿠버네티스 리소스를 생성합니다. 이미 같은 이름의 자원이 있다면 그 자원의 정보를 수정하고 configured 되었다고 출력합니다. 수정이 불가능하다면 오류를 출력합니다.
다음 시간에 계속...
출처 & 참고
이시윤 강사님의 강의