인그레스, 스토리지
Mont Kim / February 2023 (2843 Words, 16 Minutes)
3주차 Ingress, Storage
Ingress
인그레스는 클러스터 내부에 존재하는 서비스(ClusterIP, NodePort, Loadbalancer)를 외부로 노출하는 역할을 한다.
인그레스 종류 인그레스의 구현방법은 여러가지가 있고 사용편의성도 다르다
인그레스 리소스를 생성했을때 처리를 담당하는 컨트롤러다. 어떤 L7 로드밸런서를 생성할지는 이 인그레스 컨트롤러에 따라 달라진다
또한 인그레스는 다음과같이 크게 두가지로 분류된다
클러스터 외부 로드밸런서를 사용한 인그레스와
클러스터 외부 로드밸런서를 사용한 인그레스
클라우드에서 사용하는 클러스터 외부 로드밸런서를 사용한 인그레스의 경우 인그레스 리소스 생성만으로 로드밸런서의 가상IP가 할당되어 사용 할 수 있다.
따라서 인그레스의 트래픽은 ALB가 트래픽을 수신한 후 ALB에서 SSL 터미네이션이라 경로 기반 라우팅을 통해 Nodeport에 트래픽을 전송함으로써 대상 파드까지 도달한다
순서를 간략하게 하면 다음 두단계를 통해 전달된다.
- 클라이언트
- L7 로드밸런서 (NodePort 경유)
- 목적지 파드
클러스터 내부 인그레스용 파드를 배포하는 인그레스
대표적으로 ingress nginx controller가 존재한다.
클러스터 내부에 인그레스용 파드를 배포하는 인그레스 패턴은 인그레스 리소스에 정의한 L7 수준의 로드밸런싱 처리를 하기 위해 인그레스용 파드를 클러스터 내부에 생성해야한다.
또 생성한 인그레스용 파드르에 대해 클러스터 외부에서 접속할수있도록 별도로 인그레스용 파드에 LoadBalancer 서비스를 생성하는등의 준비가 필요하
그리고 인그레스용 파드가 SSL 터미네이션이나 경록로 기반 라우팅과 같은 L7 수준의 처리를 하기위해 부하에 따른 파드 레플리카 수의 오토스케일링도 고려해야한다.
인그레스용 파드에 ngnix를 사용한 nginx ingress 의 경우 로드밸런서가 nginx 파드까지 전송하고, 그다음에는 nginx가 L7 수준의 처리를 수행할 파드에 전송한다. 이때 nginx 파드에서 대상 파드까지는 nodeport를 통과하지않고 직접 파드 ip 주소로 전송된다.
순서를 간략하게하면 다음 세단계를 통해 전달된다.
- 클라이언트
- L4 로드밸런서 (type loadbalancer)
- nginx pod(nignx ingress controller)
- 목적지 파드
이번 실습의 경우 클라우드상에서 실습을 진행하기때문에
LoadBalancer를 이용하여 AWS Load Balncer Controller + ingress (ALB) IP 로 동작을 구현했다.
온프레미스에서의 Ingress는 대표적으로 ingress ngnix controller를 제일 많이 사용하는듯 하다.
# Load Balancer IAM 부여
aws iam attach-role-policy --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy --role-name masters.$KOPS_CLUSTER_NAME
aws iam attach-role-policy --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy --role-name nodes.$KOPS_CLUSTER_NAME
# External DNS IAM 부여
aws iam attach-role-policy --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/AllowExternalDNSUpdates --role-name masters.$KOPS_CLUSTER_NAME
aws iam attach-role-policy --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/AllowExternalDNSUpdates --role-name nodes.$KOPS_CLUSTER_NAME
kops edit cluster
-----
spec:
certManager:
enabled: true
awsLoadBalancerController:
enabled: true
externalDns:
provider: external-dns
-----
kops update cluster --yes && echo && sleep 3 && kops rolling-update cluster
사전에 생성했던 IAM Policy를 부여하고
kops 클러스터 정보를 수정후 업데이트를 해주면 권한을 부여할 수 있다.
과제 1
Ingress(with 도메인, 단일 ALB 사용)에 PATH /mario 는 mario 게임 접속하게 설정하고, /tetris 는 tetris 게임에 접속하게 설정하고, SSL 적용 후 관련 스샷 올려주세요
우선 mario, tetris 코드는
에 공개되어있는 코드를 이용했다.
-
Mario
apiVersion: apps/v1 kind: Deployment metadata: name: mario namespace: games labels: app: mario spec: replicas: 1 selector: matchLabels: app: mario template: metadata: labels: app: mario spec: containers: - name: mario image: pengbai/docker-supermario --- apiVersion: v1 kind: Service metadata: name: mario namespace: games annotations: alb.ingress.kubernetes.io/healthcheck-path: /mario/index.html spec: selector: app: mario ports: - port: 80 protocol: TCP targetPort: 8080 type: NodePort externalTrafficPolicy: Local
-
Tetris
# tetris.yaml apiVersion: apps/v1 kind: Deployment metadata: name: tetris namespace: games labels: app: tetris spec: replicas: 1 selector: matchLabels: app: tetris template: metadata: labels: app: tetris spec: containers: - name: tetris image: bsord/tetris --- apiVersion: v1 kind: Service metadata: name: tetris namespace: games annotations: alb.ingress.kubernetes.io/healthcheck-path: /tetris/index.html spec: selector: app: tetris ports: - port: 80 protocol: TCP targetPort: 80 type: NodePort
mario와 tetris 서비스와 인그레스로 loadbalancer를 생성할때
loadbalancer에서 health check를 진행한다.
이때 health check에 사용되는 index file의 위치를 별도로 지정해주지않으면 헬스체크에 실패하기때문에
각 서비스들로 별도의 annotation을 지정해 헬스체크파일의 위치를 선언해준다.
-
Ingress
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: namespace: games name: games-ingress annotations: alb.ingress.kubernetes.io/scheme: internet-facing alb.ingress.kubernetes.io/target-type: ip alb.ingress.kubernetes.io/certificate-arn: ${CERT_ARN} alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS": 443}]' alb.ingress.kubernetes.io/healthcheck-protocol: HTTP alb.ingress.kubernetes.io/healthcheck-port: traffic-port alb.ingress.kubernetes.io/healthcheck-interval-seconds: '15' alb.ingress.kubernetes.io/healthcheck-timeout-seconds: '5' alb.ingress.kubernetes.io/success-codes: '200' alb.ingress.kubernetes.io/healthy-threshold-count: '2' alb.ingress.kubernetes.io/unhealthy-threshold-count: '2' spec: ingressClassName: alb rules: - host: ${WEBDOMAIN} http: paths: - path: /tetris pathType: Prefix backend: service: name: tetris port: number: 80 - path: /mario pathType: Prefix backend: service: name: mario port: number: 80
HTTPS 인증서를 사용하기위해 CERT_ARN 키를 이용하고
https 관련 코드들을 추가한다.
kubectl create namespace games
kubectl namespace games
# 애플리케이션 배포
kubectl apply -f mario.yaml
kubectl apply -f tetris.yaml
# 인증서 가져오기
CERT_ARN=`aws acm list-certificates --query 'CertificateSummaryList[].CertificateArn[]' --output text`
# External DNS 등록하기
WEBDOMAIN=game.mont-kim.copm
WEBDOMAIN=$WEBDOMAIN envsubst < ingress.yaml | kubectl apply -f -
CERT_ARN 동적으로 선언할경우, CERT_ARN이 여러개 등록되어있을때 오동작 할 수 있다.
이경우 수동으로 등록을 진행해야합니다
위에 말씀드린 Ingress의 특징대로
Storage
과제 2
hostPath 고치기
hostpath 형태의 데이터 저장소의 문제점
- 파드 배포된 워커노드 drain 해서 문제 확인 → 다시 원복
-
local-path.yaml
apiVersion: v1 kind: Namespace metadata: name: local-path-storage --- apiVersion: v1 kind: ServiceAccount metadata: name: local-path-provisioner-service-account namespace: local-path-storage --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: local-path-provisioner-role rules: - apiGroups: [ "" ] resources: [ "nodes", "persistentvolumeclaims", "configmaps" ] verbs: [ "get", "list", "watch" ] - apiGroups: [ "" ] resources: [ "endpoints", "persistentvolumes", "pods" ] verbs: [ "*" ] - apiGroups: [ "" ] resources: [ "events" ] verbs: [ "create", "patch" ] - apiGroups: [ "storage.k8s.io" ] resources: [ "storageclasses" ] verbs: [ "get", "list", "watch" ] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: local-path-provisioner-bind roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: local-path-provisioner-role subjects: - kind: ServiceAccount name: local-path-provisioner-service-account namespace: local-path-storage --- apiVersion: apps/v1 kind: Deployment metadata: name: local-path-provisioner namespace: local-path-storage spec: replicas: 1 selector: matchLabels: app: local-path-provisioner template: metadata: labels: app: local-path-provisioner spec: nodeSelector: kubernetes.io/hostname: "i-0d6c79429bae74919" tolerations: - effect: NoSchedule key: node-role.kubernetes.io/control-plane operator: Exists serviceAccountName: local-path-provisioner-service-account containers: - name: local-path-provisioner image: rancher/local-path-provisioner:v0.0.23 imagePullPolicy: IfNotPresent command: - local-path-provisioner - --debug - start - --config - /etc/config/config.json volumeMounts: - name: config-volume mountPath: /etc/config/ env: - name: POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace volumes: - name: config-volume configMap: name: local-path-config --- apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: local-path provisioner: rancher.io/local-path volumeBindingMode: WaitForFirstConsumer reclaimPolicy: Delete --- kind: ConfigMap apiVersion: v1 metadata: name: local-path-config namespace: local-path-storage data: config.json: |- { "nodePathMap":[ { "node":"DEFAULT_PATH_FOR_NON_LISTED_NODES", "paths":["/data/local-path"] } ] } setup: |- #!/bin/sh set -eu mkdir -m 0777 -p "$VOL_DIR" teardown: |- #!/bin/sh set -eu rm -rf "$VOL_DIR" helperPod.yaml: |- apiVersion: v1 kind: Pod metadata: name: helper-pod spec: containers: - name: helper-pod image: busybox imagePullPolicy: IfNotPresent
-
localpath-fail.yaml
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: localpath-claim spec: accessModes: - ReadWriteOnce resources: requests: storage: 2Gi storageClassName: "local-path" --- apiVersion: apps/v1 kind: Deployment metadata: name: date-pod labels: app: date spec: replicas: 1 selector: matchLabels: app: date template: metadata: labels: app: date spec: terminationGracePeriodSeconds: 3 containers: - name: app image: centos command: ["/bin/sh"] args: ["-c", "while true; do echo $(date -u) >> /data/out.txt; sleep 5; done"] volumeMounts: - name: pod-persistent-volume mountPath: /data volumes: - name: pod-persistent-volume persistentVolumeClaim: claimName: localpath-claim
위에 첨부한 local-path.yaml파일을 통해
local path를 위한 사전작업을 실행한다.
Deployment에 nodeselector와 configmap의 데이터 path는 별도로 수정이 필요하다
kubectl apply -f ~/pkos/3/localpath-fail.yaml
위에 첨부한 yaml파일은 local-path를 이용한
# 모니터링
watch kubectl get pod,pv,pvc -owide
# 디플로이먼트
kubectl apply -f ~/pkos/3/localpath-fail.yaml
# 배포 확인
kubectl exec deploy/date-pod -- cat /data/out.txt
# 파드가 배포된 워커노드 변수 지정
PODNODE=$(kubectl get pod -l app=date -o jsonpath={.items[0].spec.nodeName})
echo $PODNODE
i-0898acf5c6da94a3e
# 파드가 배포된 워커노드에 장애유지 보수를 위한 drain 설정
kubectl drain $PODNODE --force --ignore-daemonsets --delete-emptydir-data && kubectl get pod -w
# 상태 확인
kubectl get node
kubectl get deploy/date-pod
kubectl describe pod -l app=date | grep Events: -A5
# local-path 스토리지클래스에서 생성되는 PV 에 Node Affinity 설정 확인
kubectl describe pv
...
Node Affinity:
Required Terms:
Term 0: kubernetes.io/hostname in [i-0898acf5c6da94a3e]
...
# 파드가 배포된 워커노드에 장애유지 보수를 완료 후 uncordon 정상 상태로 원복 Failback
kubectl uncordon $PODNODE && kubectl get pod -w
kubectl exec deploy/date-pod -- cat /data/out.txt
- 다음 실습을 위해서 파드와 PVC 삭제
결국 hostpath로 pv를 이용할경우
- 노드가 바뀔경우 데이터 저장소를 읽을수 없는 문제가 발생한다.
그리고 아마도 같은 path에 중복으로 hostpath를 생성할경우 데이터가 중복될것이라 예상한다.
공식 홈페이지에서도 단일노드에서 임시적으로 사용하는경우에만 권하고있으며
사용자체를 권장하는편이 아니다.
가급적으로 사용을 할경우 ReadOnly 옵션을 이용해서 데이터를 읽는경우에 사용을 권장하는데,
이경우엔 Configmap을 이용하는게 더 편할지도 모르겠다
kubestr 성능테스트
Kubestr을 이용해 스토리지의 성능테스트를 진행 할 수 있다.
단위는 IOPS로 스토리지별 성능차이를 확인 할 수 있다.
이번 실습은 NVME SSD로 프로비저닝되어 local-path를 확인하기위해 C5d.Large로 프로비저닝을 진행했다.
설치
wget https://github.com/kastenhq/kubestr/releases/download/v0.4.36/kubestr_0.4.36_Linux_amd64.tar.gz
tar xvfz kubestr_0.4.36_Linux_amd64.tar.gz && mv kubestr /usr/local/bin/ && chmod +x /usr/local/bin/kubestr
성능지표
rrqm/s : 디바이스 큐에 대기중인 초당 읽기 요청 건수
wrqm/s : 디바이스 큐에 대기중인 초당 쓰기 요청 건수
r/s : 디바이스에 요청한 초당 읽기 요청 건수
rMB/s : 디스크에 읽은 MB 수
wMB/s : 디스크에서
await: 디바이스에서 처리에 필요한 평균 IO시간 (평균응답시간)
Read test
curl -s -O https://raw.githubusercontent.com/wikibook/kubepractice/main/ch10/fio-read.fio
kubestr fio -f fio-read.fio -s local-path --size 10G
명령어를 실행하면 내부에 pod이 생겨 10G 사이즈를 읽는데 걸리는 속도를 측정한다.
curl -s -O https://raw.githubusercontent.com/wikibook/kubepractice/main/ch10/fio-write.fio
kubestr fio -f fio-write.fio -s local-path --size 10G
읽기와 쓰기 모두 IOPS가 3023이 나왔는데
4K 디스크 블록 기준 보편적으로 우리가 사용하는 MB단위로 환산을하면
3203 IOPS * 4KiB 1024 = 12.5MiB
nvme ssd를 이용했지만 물리적인 PC에서 체감가능한 속도와는 차이가 꽤 있는듯하다.
분명 Read는 더 높은 성능이 나온다고 했던거같은데…
항상 그런것은 아닌가보다
과제 3
목표
: AWS EBS를 PVC로 사용 후 온라인 볼륨 증가 후 관련 스샷 올려주세요
AWS EBS를 PVC로 사용후 볼륨 증가시키기
-
awsebs-pvc.yaml
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: ebs-claim spec: accessModes: - ReadWriteOnce resources: requests: storage: 4Gi
-
absebs-pod.yaml
apiVersion: v1 kind: Pod metadata: name: app spec: terminationGracePeriodSeconds: 3 containers: - name: app image: centos command: ["/bin/sh"] args: ["-c", "while true; do echo $(date -u) >> /data/out.txt; sleep 5; done"] volumeMounts: - name: persistent-storage mountPath: /data volumes: - name: persistent-storage persistentVolumeClaim: claimName: ebs-claim
AWS EBS를 4Gi 로 생성해 이 PV를 붙인 POD를 생성한다
# 파드 내에서 볼륨 정보 확인
kubectl exec -it app -- sh -c 'df -hT --type=ext4'
Filesystem Type Size Used Avail Use% Mounted on
/dev/nvme1n1 ext4 3.9G 16M 3.8G 1% /data
/dev/root ext4 124G 4.9G 120G 4% /etc/hosts
이제 볼륨을 10기가로 증설해본다!
kubectl patch pvc ebs-claim -p '{"spec":{"resources":{"requests":{"storage":"10Gi"}}}}'
다시 볼륨정보를 확인해보면
10기가로 늘어난것을 확인 할 수 있다.
다만 주의점은 한번 패치된 볼륨은 6시간동안 변경이 제한된다.
과제 4
목표
: AWS Volume SnapShots 실습 후 관련 스샷 올려주세요
snapshot controller 설치
# kOps 클러스터 편집
kops edit cluster
-----
spec:
snapshotController:
enabled: true
-----
kops update cluster --yes && sleep 3 && kops rolling-update cluster
볼륨 snapshot을 생성하는덴 snapshotcontroller와 certmanager가 활성화 되어있어야한다.
certmanager는 위에서 생성했기때문에 snapshotController만 추가한다
kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/aws-ebs-csi-driver/master/examples/kubernetes/snapshot/manifests/classes/snapshotclass.yaml
kubectl get volumesnapshotclass
공식 사이트에있는 aws-ebs-csi 드라이버를 이용해 볼륨 스냅샷을 만든다.
volumesnapshot.yaml
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
name: ebs-volume-snapshot
spec:
volumeSnapshotClassName: csi-aws-vsc
source:
persistentVolumeClaimName: ebs-claim
snapshot 파일을 실행시켜 스냅샷을 생성한다.
snapshot 생성이 완료되면 기존의 pod을 삭제해본다
kubectl delete pod app && kubectl delete pvc ebs-claim
이제 다시 snapshot을 사용해 pv를 생성한다.
-
ebs-snapshot-restored-claim.yaml
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: ebs-snapshot-restored-claim spec: accessModes: - ReadWriteOnce resources: requests: storage: 4Gi dataSource: name: ebs-volume-snapshot kind: VolumeSnapshot apiGroup: snapshot.storage.k8s.io
-
ebs-snapshot-restored-pod.yaml
apiVersion: v1 kind: Pod metadata: name: app spec: containers: - name: app image: centos command: ["/bin/sh"] args: ["-c", "while true; do echo $(date -u) >> /data/out.txt; sleep 5; done"] volumeMounts: - name: persistent-storage mountPath: /data volumes: - name: persistent-storage persistentVolumeClaim: claimName: ebs-snapshot-restored-claim(
pv와 pod를 모두 복구한 snapshot을 생성 할 수 있다.