Skip to Content
Archive인그레스, 스토리지

3주차 Ingress, Storage

Ingress

인그레스는 클러스터 내부에 존재하는 서비스(ClusterIP, NodePort, Loadbalancer)를 외부로 노출하는 역할을 한다.

인그레스 종류 인그레스의 구현방법은 여러가지가 있고 사용편의성도 다르다

인그레스 리소스를 생성했을때 처리를 담당하는 컨트롤러다. 어떤 L7 로드밸런서를 생성할지는 이 인그레스 컨트롤러에 따라 달라진다

또한 인그레스는 다음과같이 크게 두가지로 분류된다

클러스터 외부 로드밸런서를 사용한 인그레스와

클러스터 외부 로드밸런서를 사용한 인그레스

클라우드에서 사용하는 클러스터 외부 로드밸런서를 사용한 인그레스의 경우 인그레스 리소스 생성만으로 로드밸런서의 가상IP가 할당되어 사용 할 수 있다.

따라서 인그레스의 트래픽은 ALB가 트래픽을 수신한 후 ALB에서 SSL 터미네이션이라 경로 기반 라우팅을 통해 Nodeport에 트래픽을 전송함으로써 대상 파드까지 도달한다

순서를 간략하게 하면 다음 두단계를 통해 전달된다.

  1. 클라이언트
  2. L7 로드밸런서 (NodePort 경유)
  3. 목적지 파드

클러스터 내부 인그레스용 파드를 배포하는 인그레스

대표적으로 ingress nginx controller가 존재한다.

클러스터 내부에 인그레스용 파드를 배포하는 인그레스 패턴은 인그레스 리소스에 정의한 L7 수준의 로드밸런싱 처리를 하기 위해 인그레스용 파드를 클러스터 내부에 생성해야한다.

또 생성한 인그레스용 파드르에 대해 클러스터 외부에서 접속할수있도록 별도로 인그레스용 파드에 LoadBalancer 서비스를 생성하는등의 준비가 필요하

그리고 인그레스용 파드가 SSL 터미네이션이나 경록로 기반 라우팅과 같은 L7 수준의 처리를 하기위해 부하에 따른 파드 레플리카 수의 오토스케일링도 고려해야한다.

인그레스용 파드에 ngnix를 사용한 nginx ingress 의 경우 로드밸런서가 nginx 파드까지 전송하고, 그다음에는 nginx가 L7 수준의 처리를 수행할 파드에 전송한다. 이때 nginx 파드에서 대상 파드까지는 nodeport를 통과하지않고 직접 파드 ip 주소로 전송된다.

순서를 간략하게하면 다음 세단계를 통해 전달된다.

  1. 클라이언트
  2. L4 로드밸런서 (type loadbalancer)
  3. nginx pod(nignx ingress controller)
  4. 목적지 파드

이번 실습의 경우 클라우드상에서 실습을 진행하기때문에

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 코드는

MSA 샘플 애플리케이션 (13종)

에 공개되어있는 코드를 이용했다.

  • 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를 이용할경우

  1. 노드가 바뀔경우 데이터 저장소를 읽을수 없는 문제가 발생한다.

그리고 아마도 같은 path에 중복으로 hostpath를 생성할경우 데이터가 중복될것이라 예상한다.

Untitled

볼륨

Untitled

공식 홈페이지에서도 단일노드에서 임시적으로 사용하는경우에만 권하고있으며

사용자체를 권장하는편이 아니다.

가급적으로 사용을 할경우 ReadOnly 옵션을 이용해서 데이터를 읽는경우에 사용을 권장하는데,

이경우엔 Configmap을 이용하는게 더 편할지도 모르겠다

kubestr 성능테스트

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 사이즈를 읽는데 걸리는 속도를 측정한다.

Untitled

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

Untitled

읽기와 쓰기 모두 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기가로 늘어난것을 확인 할 수 있다.

Untitled

다만 주의점은 한번 패치된 볼륨은 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 파일을 실행시켜 스냅샷을 생성한다.

Untitled

Untitled

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(

Untitled

pv와 pod를 모두 복구한 snapshot을 생성 할 수 있다.