CLOUDWAVE

Kubernetes Advance : Deploy, Service(Network), Volume(disk) ( 25.01.13 )

갬짱 2025. 1. 16. 10:15

Deployment = 배포전략

Pod와 ReplicaSet에 대한 선언적 업데이트를 기술 → Deployment Controller에서 의도하는 상태로 비율을 조정

 

[ 자주 사용하는 6가지의 배포전략 ]

수정후 다시 반영하는 배포 → 언어별로 재구동시간이 달라짐 ( JS, Java … ) & 업데이트를 위해 서비스를 내리면 장애가 발생

 

(1) Recreate

LB ( 요청을 분기해주는 스위치 )를 거쳐서 여러 서비스 중 하나에 분배된다.

서비스의 종료/시작 간격에 따라서 서비스 다운타임이 결정된다. ( 에러나는 시간 )

 

(2) Ramped = incremental = rolling update

서비스를 하나씩 변경

사용자는 V1으로 접속하거나 혹은 V2로 접속할수도 있다.

  • maxSurge( 최고점 ) : 동시에 존재할 수 있는 pod(동일한 이름)의 갯수를 결정 동시에 존재가능한 최대 파드 수 = maxSurge + replica
  • maxUnavailable : 서비스를 제공하지 않아도 되는 파드의 갯수 반드시 존재해야하는 파드 수 = replica - maxUnavailable

 

maxSurge와 maxUnavailable의 간격이 클수록 재가동 속도가 느려진다, 좁을수록 빨라진다.

 

⇒ 전체적인 양상은 끊김없이 서비스의 버전이 교체된다. ( 이때 session , cookie가 유지되어서 진행중인 서비스에서는 연결이 유지되어 오류가 나지는 않는다 )

 

(3) Blue/Green = Red/Black

이전버전( green )이 서비스중일때 연결없는 새로운버전( blue )이 전체모두 생성됨 → 한번에 연결을 변경함

사용자는 반드시 하나의 버전에 연결됨

 

라우팅할 대상을 ip주소가 아닌 라벨기반의 selector로 지정

쿠버네티스의

기본지원X, 서비스매시를 깔아줘야함

⇒ 전체적인 양상에 공백이 없다, 매우안정적이기에 장애가 없어 민감한 서비스에도 적합하다

⇒ 롤백시간이 짧음 : 단순하게 과거 회귀 / 원래 잘못된 노드가 배포되면(잘못짬) 모두 오염되기에 문제가 발생함 → 이전버전을 다시 배포해야되어서 더욱 복잡함

하드웨어 자원이 2배로 필요함

 

(4) Canary

이전버전과 새로운 버전을 모두 띄움( ↔ lamped와 차이 ) → 모든 서비스에 대해서 비율로 양분하며 사용한다.

새로운 서비스에 대한 테스트가 가능함

label(app)이 동일 ⇒ replica의 갯수를 : 9개 vs 1개 → 점차 늘려감 / weight 설정을 다르게 해나감

 

(5) A/B testing

condition에 따라 사용자를 구분( 모바일/데스크탑, 접속지역 ) → 다르게 분기하여 처리

⇒ 여러 버전을 동시에 서비스가능, 사용자의 특성에 맞는 서비스를 제공가능

 

(6) shadow = mirrored = dark

트래픽을 복사 → 두 가지에 모두 보냄

실제 트래픽을 weight 100으로 준 곳으로 감 → mirror로 복사된 트래픽을 보냄

 

새로운 버전에 대한 성능테스트 → 트래픽을 잘 견디는지 확인한 후에 배포

⇒ 설정 복잡, 리소스 2배

⇒ 단순복사로 처리가 안되는 경우가 많음 ( 여러 연동작업들 → mocking, stubbing으로 처리 )

다른 배포작업에 선행되는 것으로 쓰임 ( 사용자의 부정적 영향없음 )

zero 다운타임 = 사용자 입장에서 서비스가 다운되지 않음

targetd 유저 = 사용자별 전용서비스 제공가능

cloud cost = 리소스 소비

rollback 시간 = 잘못된 배포를 복구

 

쿠버네티스의 deployment

  • 쿠버네티스에서 가능한 배포전략은 2개( recreate : 전부 재가동, rolling update : n개씩 재가동 ) → 모두 RC위에서 속함
    • spec.strategy.type==Recreate 이면 새 파드가 생성되기 전에 죽는다.
    • spec.strategy.type==RollingUpdate 이면 파드를 롤링 업데이트 방식으로 업데이트( 기본설정 )
    • 나머지는 서비스매시가 추가적으로 필요함
  • 이전이미지를 저장 → rollback이 가능
  • Deployment 로 배포 후에는 ReplicaSet 을 직접 관리 하지 않고 deployment 통해서 제어 kubectl edit deploy <deploy-name> kubectl scale deploy <deploy-name> –replicas=<replica-count>

 

Deployment 실습

aws-eks 클러스트의 default namespace에서 진행

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deploy
  labels: 
    app: nginx
spec:
  replicas: 4
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx-container
        image: nginx:1.7.9
        ports:
        - containerPort: 80
  • spec-strategy : 배포전략을 기술 ( 기본 RollingUpdate )
  • spec-replicas : 복제노드 갯수, 내부적으로 ReplicaSet이 생성 Deployment → ReplicaSet → Pod의 계층 구조로 Pod가 관리
  • sepc-selector : 다른 컨트롤러( deployment, statefulset 등 )과 겹치지 않는 고유의 것
  • spec-template : 배포할 Pod에 대한 기술

(1) 커맨드라인을 이용한 배포

kubectl apply -f ./nginx-deploy.yaml

 

(2) yaml파일 수정후 배포

kubectl set image deployment.apps/nginx-deploy nginx-container=nginx:1.9.1 --record

 

 

이미지 버전 수정

—record를 적어주면 deploy에 기록되고 롤백가능

+) Flag --record has been deprecated, --record will be removed in the future

vi를 통한 편집 : 이것은 배포가 아니라 단순 pod레벨에서의 변경

kubectl edit po nginx-deploy-59db5687cb

사용자 전략에 따라 container creating = 변경중임

 

(3) 배포히스토리가 기록됨 ( 3회 )

 

kubectl rollout history deployment.apps/nginx-deploy

더욱 상세하게 기록조회

 

rollback수행도 가능

kubectl rollout undo deployment.apps/nginx-dp --to-revision=숫자

이전버전으로 돌아가기 → 빠르게 진행( 노드의 캐시에 정보가 담아있다. )

 

kubectl annotate deploy nginx-deploy [kubernetes.io/change-cause=](<http://kubernetes.io/change-cause=>)"oh my god 1.27.3"

수정전에 annotatinod를 변경하면 그 사유가 쉽게 보임 → 실제적으로 이러한 수동작업들은 api서비스를 이용해 자동적으로 관리

 

[ Deployment ScaleOut ]

5초전에 3개가 더 만들어짐, scale로 늘이기

 

scale로 줄이기

 

pod : 컨테이너를 관리

이외에 파드와 관련된 기능별로 객체를 만들어놓음

  • RC : 복제기능
  • RS : 복제기능
    • deployment : 배포기능(2개전략) + 히스토리/롤백 기능 → 애플리케이션 배포
    • statefullSet : 디스크 리소스 배포
  • DS : 복제기능, 노드 생애주기와 함께함

⇒ 모두 selector로 감시대상을 지정하고 모니터링

⇒ cascade로 RC의 것을 RS의 것들로 변경가능

 


Service

Pod 간의 통신을 안정적으로 지원하고, 외부에서 Pod로의 접근을 가능하게 만드는 네트워크 객체

 

정상적인 운영상태에서 노드가 삭제된다면 이전에 담았던 pod들이 다른 노드의 영역으로 넘어간다

이때 넘어간 pod는 새로운 pod ip로 생성된다. ( 10.2.10.2 → 10.2.10.5 )

이렇게 여러 상황( 전원 on/off도 이에 해당 )에 따라 ip는 바뀌기에 정적으로 라우팅 해결이 불가한 상황

 

Service IP 와 Pod IP

Pod네트워크 : 각 Pod의 고유한 IP주소가 정의되는 영역

Pod IP : CNI플러그인에 의해 할당구성 → 가변적, Pod에 개별적

 

Service네트워크 : 고정된 가상IP(service IP = cluster IP)를 제공되는 영역

Service IP = Cluster IP : 서비스에 의해 할당되는 불변적인 가상의 IP

사용자는 가변적인 Pod IP 대신 고정된 서비스의 IP에 접근 → kube-proxy가 서비스 IP와 실제 Pod(endpoint) IP의 매핑을 확인( iptables규칙을 생성 및 관리 ) & 트래픽을 적절한 Pod로 라우팅( 로드밸런싱 )

 

K8s 에서 제공하는 서비스의 종류

쿠버네티스의 서비스 → 4가지 종류 혹은 클러스터IP서비스만을 지칭하기도함( 가장 기본적인 서비스 )

서비스 = 일종의 네트워크 로드밸런스 서비스( 요청을 받아서 분배 ⇒ 부하분산 & 동적라우팅 & 장애 내성이 올라감 )

 

(1) 로드밸런서의 역할 : 요청이 들어오면 분배한다. IP가 변경되어도 트래픽이 전달된다.

(2) 동적라우팅의 역할 : 변화에 적절하게 대응

  • ClusterIP
  • NodePort
  • LoadBalancer
  • Ingress

이러한 서비스들의 사용목적 3가지 : 부하분산, 동적인 라우팅, 장애내성강화

+) 외부접속의 가능화 ( k8s외부 VPC + 인터넷까지 연결 )

물리적인 LB가 아니라 소프트웨어적인 LB → 이를 쿠버네티스에서 서비스라고 명명한다!

 

[ ClusterIP ]

kube-proxy기반으로 동작하며 동작모드를 선택가능하다.

  • IPTables 모드 : 라우팅은 랜덤 선택 ( netfilter를 사용하여 더욱 향상된 성능지원 )
  • IPVS 모드 : 라우팅 알고리즘 선택 가능( 라운드로빈(RR), 최소연결(LC), 목적지해싱(DH), 소스해싱(SH), 최단예상지연(SED), 큐미사용(NQ) )

모든 노드에 기본적으로 kube-proxy pod가 DaemonSet으로 떠있다. ( 현재 워커노드 3개 )

cluster IP는 일종의 로드밸런서로 여기에 접속하면 pod하나로 접속된다.

 

[ cluster IP 실습 ]

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nodeapp-deploy
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nodeapp-pod
  template:
    metadata:
      labels:
        app: nodeapp-pod
    spec:
      containers:
      - name: nodeapp-container
        image: dangtong/nodeapp
        ports:
        - containerPort: 8080

Deployment객체 : 3개의 Pod를 생성( app = nodeapp-pod ) & rollingstone방식으로 배포

apiVersion: v1
kind: Service
metadata:
  name: nodeapp-service
spec:
  ports:
  - port: 80
    targetPort: 8080
  selector:
    app: nodeapp-pod
  • 서비스 네트워크 범위에서 하나의 Cluster IP ( 서비스 IP )가 할당 ( type 생략시 clusterIP가 된다 )
  • 서비스 IP의 80번 포트로 들어오는 트래픽이 Pod의 8080번 포트(targetPort)로 전달
  • selector : Service가 연결할 Pod을 선택하는 기준을 설정( 서비스할 pod의 조건을 결정 )
    • 레이블 기반 ( 보통 DS, RC, RS의 관리조건 )
kubectl get po,deploy,svc
NAME                                      READY   STATUS    RESTARTS   AGE
pod/nodeapp-deployment-55688d9d4b-8pzsk   1/1     Running   0          2m45s
pod/nodeapp-deployment-55688d9d4b-pslvb   1/1     Running   0          2m46s
pod/nodeapp-deployment-55688d9d4b-whbk8   1/1     Running   0          2m46s

NAME                                 READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/nodeapp-deployment   3/3     3            3           2m46s

NAME                      TYPE           CLUSTER-IP       EXTERNAL-IP      PORT(S)        AGE
nodeapp-service   ClusterIP      10.101.249.42    <none>           80/TCP         78s

Deployment객체에 의해 관리되는 3개의 Pod가 생성됨

nodeapp-service 서비스가 생성됨 : cluster IP타입, 80번포트로 요청받음

 

로컬 머신에서 클러스터 내부의 서비스를 직접 접근이 불가함( 클러스터IP는 클러스터 내부에서만 유효함 )

kubectl port-forward service/nodeapp-service 8083:80

kubectl port-forward : 클러스터 내부의 서비스나 Pod와 연결하기 위한 방법 중 하나

로컬포트(8083)를 클러스터 포트(80)으로 매핑 → 클러스터IP를 통해 노드IP들로 라우팅( kube proxy ) & 8080포트로 접속 → 서비스로 접속

근데 결과적으로 한놈만 팬다..

 

[ NodePort ]

: 노드의 특정 포트를 할당 및 개방 하여 서비스(ClusterIP) 와 연동함 ( 클러스터 IP를 포함 )

  • 클러스터IP에는 직접접속이 안되고 위의 실습예시처럼 포트포워딩을 해야만 가능
  • 노드포트로 접속하면 포트포워딩없이 접속가능 ( 노드포트 생성시 클러스터 IP를 자동으로 생성해줌 & 노드의 포트와 서비스를 자동적으로 연결 및 로드밸런싱 처리 )
  • 노드포트의 할당범위가 제한 : (30000 ~ 32767 : --service-node-port-range) ⇒ product용으로 적합하지 않음

 

[ node port 실습 ]

apiVersion: v1
kind: Service
metadata:
  name: node-nodeport
spec:
  type: NodePort
  ports:
  - port: 80
    targetPort: 8080
    nodePort: 30123
  selector:
    app:  nodeapp-pod

type: NodePort ( type 생략시 clusterIP가 된다 )

nodePort: 30123

로컬환경에서 30123 포트로 접속 ( curl [<http://localhost:30123>](<http://localhost:30123/>) )

→ NodePort 서비스에 의해 클러스터 cluster IP와 포트 80으로 전달

→ Pod의 8080 포트로 트래픽을 라우팅

kubectl get po,rs,svc

기본적으로 생성된 cluser IP ( service/kubernetes ) : 모든 Pod IP를 매핑함 = 특정 Pod에만 매핑되는 것이 아니라 모든 Pod가 이 서비스의 IP를 통해 접근 가능한 방식으로 작동

 

local 환경에서 수행( aws환경에서는 수행시 방화벽 처리를 해줘야함 )

load-balancing의 효과가 있는 것을 볼 수 있다!!

 

[ LoadBalancer ]

clusterIP가 외부 로드밸런서와 연결된 형식이다. clusterIP가 외부통신이 불가능하기에 앞단의 LoadBalancer( L4 로드밸런서로 IP와 port만으로 통신가능 )와 결합하여 외부 트래픽 유입이 가능하도록 설계한다.

 

 

[ LoadBalancer 실습 ] aws환경에서 실습

apiVersion: v1
kind: Service
metadata:
  name: nodeapp-lb
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-type: external
    service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
    service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
spec:
  type: LoadBalancer
  selector:
    app: nodeapp-pod
  ports:
  - port: 80
    targetPort: 8080

type: LoadBalancer ( type 생략시 clusterIP가 된다 )

annotiation은 원래 주석의 용도이나, aws에서는 라벨(설정)로 사용된다.

  • external : VPC 외부에서 접속이 가능
  • aws-load-balancer-nlb-target-type: ip
  • 외부망 뿐아니라 인터넷망에서 접속을 가능하게 함

aws에서 생성된 로드밸런서 주소로 접속 → pod로 접속됨

+) 추가사항 : aws에서의 LB방식은 nodePort를 거쳤다가 간다.

로드밸런스의 대상이 pod가 아니라 노드포트가 된다. ⇒ 많은 주소를 성능이 나쁘지 않게 사용가능

결국 endpoint에는 pod IP들이 연결되어있다.

 

[ Ingress ]

서브도메인에 따라 서비스의 성격이 모두 다름 ( URL 기반 라우팅 ) → HTTP(S) 트래픽을 처리

  • 외부로드밸런서 : L7 로드밸런서로 domain, header까지 확인가능 ( 유일한 L7을 사용 )
  • Ingress Controller : 호스트 도메인에 따라 트래픽을 특정 서비스(Cluster IP)로 라우팅하는 역할 여러 도메인 이름(호스트)을 기반으로 요청을 다른 서비스나 애플리케이션으로 라우팅 = virtual service ( 가상도메인 서비스 ), virtual hosting( 가상호스팅 서비스 )

 

[ Ingress 실습 ]

host: nginx.duldul32.com, goapp.duldul32.com 등을 판단하여 다른 경로로 보냄

핵심은 또 다른 서비스( service )로 보낸다는것 → 서비스 이름을 기재해줘야함

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-container
        image: nginx:1.7.9
        ports:
        - containerPort: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: goapp-deployment
  labels:
    app: goapp
spec:
  replicas: 3
  selector:
    matchLabels:
      app: goapp
  template:
    metadata:
      labels:
        app: goapp
    spec:
      containers:
      - name: goapp-container
        image: dangtong/goapp
        ports:
        - containerPort: 8080
apiVersion: v1
kind: Service
metadata:
  name:  nginx-lb
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-type: external
    service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
    service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
spec:
  type: LoadBalancer
  ports:
  - port: 80
    targetPort: 80
  selector:
    app: nginx

---
apiVersion: v1
kind: Service
metadata:
  name:  goapp-lb
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-type: external
    service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
    service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
spec:
  type: LoadBalancer
  ports:
  - port: 80
    targetPort: 8080
  selector:
    app: goapp

Ingress의 서비스는 클러스터ip로 사용할수도 있고 LB로 사용할수도 있다

→ 클러스터ip로 사용하는 것을 권장한다.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx-goapp-ingress
spec:
  rules:
  - host: nginx.acorn.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: nginx-lb
            port:
              number: 80
  - host: goapp.acorn.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: goapp-lb
            port:
              number: 80

Ingress는 별도의 타입으로 정의

spec.rules에 각 host의 이름과 매핑할 서비스, 포트를 개별적으로 정의가능함

ingress를 조회하는 명령은 별도로 있다.

kubectl get ingress

해당 도메인 주소가 존재하지 않음

C:\Windows\System32\drivers\etc 경로의 host파일을 수정

→ 해당 도메인 주소를 추가함

 

+) Headless service

IP목록을 받아서 서비스가 직접 접속함

redis여러개를 클러스터화하여 운영, 노드를 해시값 범위로 분산하여 저장( 노드의 해시값을 비트값으로 변환하여 특정 redis에 저장함 )

분산형DB들의 특징 ip목록을 보고 n번째 개체에 저장되어있음을 알 수 있다.

LB처럼 움직이는 것이아니라 DNS처럼 움직이게 된다.

 

시스템영역을 조회했을떄 DNS서버가 쿠버네티스 내부에 존재함

규칙에 맞게 DNS(domain)를 생성하는 역할을 수행 → 도메인 또는 라벨셀렉터로 접속가능, 결과로 IP를 반환해준다.

 


Volume

컨테이너는 기본적으로 stateless → 저장하기 위해 볼륨을 사용

  • 디스크( storage ) - 물리적 저장공간
  • 파티션 : 쪼갠구역 → 그 크기에 따라 다르다 ( 통째가 볼륨일수도 있고 일부분크기로 잡은 것이 볼륨일수도 있음 )
  • volume - 논리적 저장공간

레이어에 해쉬값이 존재하는 이유 → 캐시이용을 위해 ( 변경사항을 확인하고 다시 가져오지 않기 위함 )

 

Pod 의 일부로 정의 되며, Pod 와 라이프사이클을 같이함

독립적인 쿠버네티스 오브젝가 아니며 스스로 생성 하거나 삭제할 수 없다(kind 가 없다) → mount를 통해서만 사용가능

 

쿠버네티스의 볼륨종류

(1) empty dir

: 쿠버네티스에서 지원하는 기본 볼륨 → pod가 종료되면 함께 삭제됨( pod와 동일한 life cycle을 가짐 )

  • 저장의 목적이 아닌 정렬( sorting ) 등 데이터 처리의 목적
  • Pod내부의 파일교환
  • 임시파일 보관

 

(2) hostpath

: pod가 종료되어도 상태유지 → 그러나 Pod의 노드위치가 변하면 참조불가

pod가 삭제되면 다른 노드에서 살아날 가능성 → 그러나 다른 노드로 이동하면 참고할게 없어져서 안쓴다.

유일하게 daemonset은 이를 사용할 수 있음 ( 노드와 라이프사이클을 같이함 )

 

 

(3) 이외의 특수볼륨

  • gitRepo : http를 이용하여 git의 repo를 컨테이너 디렉토리에 mount가능 → 즉시 서비스 가능, github pages( markdown만 지원 )

  • configMap : 설정파일 및 환경변수를 보관하는 볼륨
  • secret : 기밀유지가 필요한 설정파일, 환경변수를 보관하는 볼륨

 

Persistent Volume(PV)

: 영구볼륨 = 쿠버네티스 외부의 디스크를 사용가능

Persistent Volume Access Mode

  • ReadWriteOnce(RWO) : 하나의 노드에서만 읽기/쓰기 가능 → 노드 내의 여러 Pod에서 접근가능 → pod의 경우 죽고 나서 다른 노드에 뜨고 된다면 공유가 끊김
  • ReadOnlyMany(ROX) : 여러 노드에서 읽기만 가능
  • ReadWriteMany(RWX) : 여러 노드에서 읽기/쓰기 가능
  • ReadWriteOncePod(RWOP)가 추가됨 : 하나의 파드에서만 읽기/쓰기 가능 → 유일하게 pod기준으로 실행한다.
  • 쿠버네티스의 pod특징 ( pod내 컨테이너는 네트워크를 공유 & 스토리지 공유 ) → 컨테이너 개별적인 보안을 유지하기 위해 최근에 추가된 모드

 

 

디스크의 종류 3가지

  • 블록 디스크 : 포맷이 지정되지 않음 → 크기로 할당 ( AWS의 EBS ) read write once만 가능( 빠르게 읽고 쓰지만 공유가 어려움 )
  • 네트워크 파일 시스템 : nfs, samba read write many( 네트워크를 통해 누구나 읽고 쓸 수 있음 )
  • 오브젝트 스토리지 : AWS의 S3 create & append만 가능함(update불가) 왜? 동기화가 어려움(대용량처리를 위해 기능보다 성능을 선택), 그러나 가용성이 매우 좋음( 99% 보존 ) ⇒ 단일디스크에서는 느리지만 대용량 작업에서는 분산처리(병렬)로 인해 큰 작업에서는 빠를 수 있다.

성능 : 블록 > 네트워크 > 오브젝트

 

PersistentVolume 과 PersistentVolumeClaim

(1) admin이 물리적인 스토리지를 구축 ( NFS 등의 공유가능한 디스크 )

(2) admin이 쿠버네티스 내에서 YAML 디스크립터를 통해 PV를 생성

PersistentVolumeReclaimPolicy

  • Retain (유지) : PVC를 삭제해도 PV를 유지함 → 데이터 보존가능 but 수동정리가 필요
  • Delete (삭제) : 관련된 볼륨 모두 삭제 ( 기본값 )
  • Recycle (재사용) : 볼륨자체는 유지하나 모든 데이터를 삭제

(3) user가 PVC( 요청서 객체, persistent volumen claim )를 생성

(4) 쿠버네티스가 user의 PVC에 맞는 PV를 찾아와서 바인딩 시킴( 마운트 )

(5) 사용자는 볼륨을 포함하는 Pod를 생성

 

[ PV 및 PVC 사용 ]

운영자가 디스크를 만들고 PV를 구축 → 요청서( 크기, 조건으로 요청 / Pod생성시 제출하면 알아서 붙여준다 ) → 적절한 인스턴스 Pod가 생성됨

 

쿠버네티스에서 볼륨을 생성하는 방법

(1) volume 직접 생성

(2) PVC를 통한 volume 간접 생성

(3) SC기능을 이용하여 더욱 간편한 간접생성

 

Dynamic Provisioning

Storage Class를 활용하여 동적으로 요청한 크기를 할당하는 쿠버네티스의 스토리지 기능, admin이 사전에 직접 쪼개놓는 작업이 필요없음 ( 자동모드 ) 

<-> 운영자가 디스크를 볼륨으로 쪼개놓음( PV : persisten volume 생성 ) → PVC의 요청과 일치하는 것이 있으면 할당 ( 수동모드 )

 

[ 수동모드 실습( static provisioning ) ]

 

[1] AWS 볼륨생성

aws ec2 create-volume --volume-type gp2 --size 80 --availability-zone ap-northeast-2a
AvailabilityZone: ap-northeast-2a

80GB크기의 볼륨을 생성, 타입은 GP2로 생성

볼륨ID vol-02a5de5fb8166f39f를 얻을 수 있다.

## 조회
aws ec2 describe-volumes --filters Name=status,Values=available Name=availability-zone,Values=ap-northeast-2a

## 삭제
aws ec2 delete-volume --volume-id vol-02a5de5fb8166f39f

 

[2] PV를 수동으로 생성

# AWS
apiVersion: v1
kind: PersistentVolume
metadata:
   name: mongodb-pv
spec:
  capacity:
    storage: 1Gi
  csi:
    driver: ebs.csi.aws.com
    fsType: ext4
    volumeHandle: vol-xxxxxxxxxxxxx
  accessModes:
  - ReadWriteOnce
  - ReadOnlyMany
  persistentVolumeReclaimPolicy: Retain
  nodeAffinity:
    required:
      nodeSelectorTerms:
        - matchExpressions:
            - key: topology.ebs.csi.aws.com/zone
              operator: In
              values:
                - ap-northeast-2a

디스크와 PV는 특정 노드에 직접 올라가는 것이 아니라, Pod와 연결되는 방식으로 특정 노드와 관계를 맺는 개념

AWS에서는 노드가 AZ에 종속적으로 생성( 분산배치와 고가용성 ) → 노드와 PV(볼륨)이 동일한 AZ(가용 영역)에 존재하도록 해야함 → EBS 시스템과 연결되기 좋음: AZ 내에서 데이터를 안정적으로 저장하고, EC2 인스턴스(노드)와 연결

  • driver: ebs.csi.aws.com
    EBS( elasic block service )는 zonal service임( 층별구성 : northeast-2a, northeast-2c ) = 가용 영역(AZ) 단위로 동작
    ↔ EFS( network FS의 일종 )는 regional service( northeast-2까지만 지정 )
  • kind: PersistentVolume ( PV리소스 생성 )
  • 최소 1Gi
  • 파일시스템 포맷은 ext4
  • access mode : 여러노드에서 읽을 수 있음, 하나의 노드에서만 쓰기가능
  • persistentVolumeReclaimPolicy: Retain
    • persistentVolumeReclaimPolicy : PV가 더 이상 필요하지 않을 때( 연결된 pod가 삭제될때 ) Kubernetes가 해당 PV를 어떻게 처리할지를 결정
    • pod가 삭제되더라도 디스크에 기록된다. ( 데이터 유지 )
  • nodeAffinity : Kubernetes에서 PersistentVolume(PV)을 특정 노드에 연결하거나 제한하는 데 사용되는 설정 → AZ 위치( 노드가 위치한 특정 층을 지정, ap-northeast-2a )
    node selector를 통해 특정 서브넷(노드위치)에 뜨도록 제어 
    ⇒ key(AZ)=value 일치하는 서브넷(노드)에서만 뜨게 된다.

 

[3] 요청서(PVC) 생성

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mongodb-pvc
spec:
  resources:
    requests:
      storage: 40Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  storageClassName: ""

요청량을 기재, 요청모드를 기재

local에는 disk provider가 존재하지 않아서 할당되지 않기에 aws환경에서 실행

SC를 사용하지 않음 → 수동할당

 

[4] Pod생성

apiVersion: v1
kind: Pod
metadata:
  name: mongodb
spec:
  containers:
  - image: mongo
    name: mongodb
    volumeMounts:
    - name: mongodb-data
      mountPath: /data/db
    ports:
    - containerPort: 27017
      protocol: TCP
  volumes:
  - name: mongodb-data
    **persistentVolumeClaim:
      claimName: mongodb-pvc**

mongoDB서비스의 Pod를 생성

  • 볼륨생성 : 생성한 PVC객체를 적용한 볼륨을 생성 ( persistentVolumeClaim = PVC )
  • datafile의 경로( /data/db )에 볼륨을 마운트 ⇒ 그 내용을 유지할 수 있게함
  • 기본포트 27017

 

Pending : 요청서만 존재하는 상태, 요청서를 물고 있는 어플리케이션이 존재하지 않음

적용했지만 계속 pending상태임 → 왜? pod가 뜰 수 있는 조건의 node가 없음

라벨이 붙은 노드에만 뜰 수 있음 → 적절한 노드에 라벨 부여

 

첫번째 노드의 위치가 일치하는 2a영역이므로 첫번째 노드에 라벨을 부여

kubectl label nodes ip-10-1-3-141.ap-northeast-2.compute.internal az=a

 

pod삭제후 재생성

kubectl delete po mongodb
kubectl apply -f ./mongodb-pod.yaml

 

[ 동적생성모드 실습 ]

운영자가 디스크 및 PV를 구축까지 자동으로 생성됨 → 재사용가능한 storage class가 자동으로 할당

 

[1] storage class를 생성

kubectl get sc

storage class를 조회

WaitForFirstConsumer상태 : 현재 1개가 조회( gp2 ) → storage class객체를 추가적으로 생성가능, pod의 zone에 해당하는 디스크의 PV가 자동적으로 할당됨( 스토리지와 노드의 지역성을 최적화 )

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast
provisioner: ebs.csi.aws.com
volumeBindingMode: Immediate
reclaimPolicy: Retain
parameters:
  csi.storage.k8s.io/fstype: ext4
  type: gp2
allowedTopologies:
  - matchLabelExpressions:
    - key: topology.ebs.csi.aws.com/zone
      values:
      - ap-northeast-2a
      - ap-northeast-2c

reclaimPolicy의 3가지 모드 : retain, recycle, delete ⇒ DB로 사용하기 위해서는 값이 보존되어야함 Retain으로 설정

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast
provisioner: ebs.csi.aws.com
reclaimPolicy: Retain
parameters:
  csi.storage.k8s.io/fstype: ext4
  type: gp2
volumeBindingMode: WaitForFirstConsumer

allowedTopologies 설정 없이 WaitForFirstConsumer 설정으로 Pod가 스케줄링된 노드의 지역성(Zone)에 따라 자동으로 적합 PV를 바인딩

 

[2] dynamic-pvc생성

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
   name: mongodb-pvc-dynamic
spec:
  **storageClassName: fast**
  resources:
    requests:
      storage: 2Gi
  accessModes:
    - ReadWriteOnce

사용할 storageClassName을 기재

 

[3] mongodb pod생성

apiVersion: v1
kind: Pod
metadata:
  name: mongodb
spec:
  containers:
  - image: mongo
    name: mongodb
    volumeMounts:
    - name: mongodb-data
      mountPath: /data/db
    ports:
    - containerPort: 27017
      protocol: TCP
  volumes:
  - name: mongodb-data
    persistentVolumeClaim:
      claimName: mongodb-pvc-dynamic

파드내 볼륨 생성시 PVC를 적절하게 매핑

kubectl apply -f ./mongodb-pod.yaml

 

SC만 생성하면 pod에 알아서 PV를 붙여준다.

 

statefulSet과 같은 Pod( DB종류, 캐시저장 애플리케이션 )를 사용할때 이러한 방식으로 PV를 볼륨으로 할당받는다.

 

ConfigMap

  • 모든 애플리케이션에서는 설정파일이 필요함 ( nginx ⇒ /etc/nginx/conf 내의 default.conf, springboot ⇒ properties.yaml, mysql ⇒ my.conf )
  • 위치에 따라 설정파일의 내용이 변경됨 : 각 단계에서 설정값이 변경됨 ( 개발계 → 검증계 → 운영계, 내부적으로 n차씩 수행 ) & 실행환경에 따라서 변경됨 ( 로컬PC, 배포서버 )
  • ConfigMap : 가변적인 설정파일을 애플리케이션과 별도로 분리하는 쿠버네티스 객체 각 애플리케이션의 설정파일 위치(ex. nignx의 경우 /etc/nginx/conf )로 mount하면 설정파일이 쉽게 변경됨

쿠버네티스의 운영 : 네임스페이스 관점의 분리 & 클러스터 관점의 분리

  • 네임스페이스 : 개발ns, 검증ns, 운영ns로 구축
  • 클러스터 : 개발cl, 검증cl, 운영cl로 구축

기본적으로 네임스페이스 레벨에서 worker nod, configMap 리소스가 정의됨( 특정 클러스터-특정 네임스페이스에 종속 ) ⇒ 네임스페이스별 다른 configMap 설정으로 구동가능

 

[ config 실습 ]

#!/bin/bash
trap "exit" SIGINT
INTERVAL=$1 #파라메터로 첫번째 매개 변수를 INTERVAL 로 저장
echo "Configured to generate neew fortune every " $INTERVAL " seconds"
mkdir /var/htdocs
while :
do
    echo $(date) Writing fortune to /var/htdocs/index.html
    /usr/games/fortune  > /var/htdocs/index.html
    sleep $INTERVAL
done

$1은 두번째 파라미터를 의미함 ( 명령어 그 자체가 $0을 의미함 ) → 이를 환경변수로 선언하고 그만큼 sleep하도록하며 해당 간격으로 동작을 수행(새로운 "fortune" 메시지를 생성하고, 이를 /var/htdocs/index.html 파일에 저장)하도록 하는 쉘 스크립트

FROM ubuntu:latest 
RUN apt-get update; apt-get -y install fortune 
ADD fortuneloop.sh /bin/fortuneloop.sh 
RUN chmod 755 /bin/fortuneloop.sh 
ENTRYPOINT ["/bin/fortuneloop.sh"] 
CMD ["10"] # args가 없으면 10초
  • 우분투 환경에서 해당동작을 수행하는 프로그램 이미지를 생성
  • 빌드 시에 fortune프로그램 설치
    fortune : 재미있는 격언이나 문장을 출력하는 프로그램
  • chmod 755 /bin/fortuneloop.sh → 빌드시에 쉘스크립트에 실행가능한 권한 부여
  • entrypoint + cmd(default 인자를 설정하는 목적 & 오버라이드 목적)가 조합되어 명령어로 사용된다
docker build -t rudalsss/fortune-args .
docker login -u rudalsss
docker push rudalsss/fortune-args

이미지 생성 및 레지스트리 업로드 → 쿠버네티스 pod에서 pull하여 사용가능

 

방법1 : docker 매개변수(CMD) 이용

컨테이너에서 image를 PULL할때 매개변수(arg)를 설정한다.

apiVersion: v1
kind: Pod
metadata:
  name: fortune5s
spec:
  containers:
  - image: rudalsss/fortune-args
    args: ["5"] # 이미지에 $1의 매개변수로 전달
    name: html-generator
    volumeMounts:
    - name: html
      mountPath: /var/htdocs
  - image: nginx:alpine
    name: web-server
    volumeMounts:
    - name: html
      mountPath: /usr/share/nginx/html
      readOnly: true
    ports:
    - containerPort: 80
      protocol: TCP
  volumes:
  - name: html
    emptyDir: {}

fortune5s Pod : html-generator(rudalsss/fortune-args) & web-server(nginx) 컨테이너 2개로 구성됨

→ 동일한 pod내에서 컨테이너는 네트워크, 볼륨을 공유한다.

emptyDir볼륨( pod기본 볼륨 ) : 2곳에 mount ( /var/htdocs, /usr/share/nginx/html )

html-generator에서 지정된(arg) 시간간격마다 볼륨에 명언을 올림 → web-server에 접속하여 이를 열람가능

 

kubectl exec -it fortune5s -c html-generator -- bash

html-generator에 접속하여 bash쉘을 수행한다 → 볼륨경로로 가면 index.html을 생성하고 있다.

 

kubectl exec -it fortune5s -c web-server -- sh

web-server에 접속하여 sh쉘을 수행한다 → 볼륨 경로로 가면 index.html이 존재한다 & tail로 읽어보면 실시간으로 5초간격으로 내용이 추가됨을 알 수 있다.

 

[ 추가문제 ]

(1) pod를 Deployment로 만들어라( replica = 3 )

apiVersion: apps/v1
kind: Deployment
metadata:
  name: fortune-deploy
  labels:
    app: fortune-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: fortune-app
  strategy:
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: fortune-app
    spec:
      containers:
      - image: rudalsss/fortune-args
        args: ["5"]
        name: html-generator
        volumeMounts:
        - name: html
          mountPath: /var/htdocs
      
      - image: nginx:alpine
        name: web-server
        volumeMounts:
        - name: html
          mountPath: /usr/share/nginx/html
        ports:
        - containerPort: 80
          protocol: TCP
      volumes:
        - name: html
          emptyDir: {}

 

(2) 이에 접근가능한 LB.yaml만들기

apiVersion: v1
kind: Service
metadata:
  name: fortuneapp-lb
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-type: external
    service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
    service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
spec:
  type: LoadBalancer
  selector:
    app: fortune-app
  ports:
  - port: 80
    targetPort: 80
    protocol: TCP

LB주소로 접근하면 3개의 Pod중 하나에 접근하여 명언읽기

LB로 접근해야하는 것은 web-server어플리케이션( 어차피 포트를 뚫어준게 이거밖에 없음 ) → Nginx에 접근시 자동으로 index.html을 반환( 기본 설정 )

 

방법2 : 컨테이너의 환경변수($) 이용

컨테이너에 넘겨주는 것이 아니라 컨테이너 OS에 넘겨준다.

컨테이너에서 image를 PULL할때 환경변수(env)를 설정한다.

apiVersion: v1
kind: Pod
metadata:
  name: fortune30s
spec:
  containers:
  - image: dangtong/fortune:env
    **env:
    - name: INTERVAL 
      value: "30"**
    name: html-generator
    volumeMounts:
    - name: html
      mountPath: /var/htdocs
  - image: nginx:alpine
    name: web-server
    volumeMounts:
    - name: html
      mountPath: /usr/share/nginx/html
      readOnly: true
    ports:
    - containerPort: 80
      protocol: TCP
  volumes:
  - name: html
    emptyDir: {}

 

방법3 : configMap객체를 환경변수로 설정

kubectl get cm

default namespace에 존재하는 config map을 보여줌

kubectl get cm -A

모든 namespace에 존재하는 config map을 보여줌

 

config map을 생성(1)

kubectl create configmap

--from-literal : 문자로 값을 부여 ( key=value쌍으로 반복적으로 부여가능 )

apiVersion: v1
kind: Pod
metadata:
  name: fortune7s
spec:
  containers:
  - image: dangtong/fortune:env
    **env:
    - name: INTERVAL
      valueFrom:
        configMapKeyRef:
          name: fortune-config
          key: sleep-interval**
    name: html-generator
    volumeMounts:
    - name: html
      mountPath: /var/htdocs
  - image: nginx:alpine
    name: web-server
    volumeMounts:
    - name: html
      mountPath: /usr/share/nginx/html
      readOnly: true
    ports:
    - containerPort: 80
      protocol: TCP
  volumes:
  - name: html
    emptyDir: {}

환경변수중 INTERVAL은 생성한 configMap객체로부터 주입받음

fortune-config이라는 이름의 configMap객체에서 key인 sleep-interval의 value에 해당하는 7의 값이 설정됨

환경변수가 적절하게 삽입되었음을 확인할 수 있다!!

 

config map을 생성(2) conf파일에 configMap을 정의하고 → kubectl create configmap 이름 --from-file

server {
    listen                8080;
    server_name        www.acron.com;

    gzip on;
    gzip_types text/plain application/xml;
    location / {
        root    /usr/share/nginx/html;
        index    index.html index.htm;
    }
}

nginx 설정파일을 cm-with-dir/config-dir 하에 저장한다.

kubectl create configmap fortune-config2 --from-file=conf파일

디렉토리(파일)로부터 읽어서 configmap객체를 만든다.

 

출력해보면 설정내용이 그대로 들어가 있다.

key = 파일이름 / value = 정의내용의 값을 가지게 된다.

 

 

apiVersion: v1
kind: Pod
metadata:
  name: fortune7s
spec:
  containers:
  - image: dangtong/fortune:env
    **env:**
    - **name: INTERVAL
      valueFrom:
        configMapKeyRef:
          name: fortune-config2
          key: sleep-interval**
    name: html-generator
    volumeMounts:
    - name: html
      mountPath: /var/htdocs
  - image: nginx:alpine
    name: web-server
    volumeMounts:
    - name: html
      mountPath: /usr/share/nginx/html
      readOnly: true
    ports:
    - containerPort: 80
      protocol: TCP
  volumes:
  - name: html
    emptyDir: {}

INTERVAL이라는 이름의 환경변수를 configMap을 이용하여 세팅한다

env-valueForm-configMapKeyRef

 

방법4 : configMap with directory

[ confmap으로 dir을 이용 ]

apiVersion: v1
kind: Pod
metadata:
  name: nginx-configmap-vol
  labels:
    name: nginx-configmap-vol
spec:
  volumes:
  - name: config
    configMap:
      name: fortune-config2
  containers:
  - name: nginx-configmap-vol
    image: nginx:1.7.9
    volumeMounts:
    - name: config
      **mountPath: /etc/nginx/conf.d**
      readOnly: true
    resources:
      limits:
        memory: "128Mi"
        cpu: "500m"
    ports:
      - containerPort: 8080

configMap을 볼륨으로 선언 → conf.d 디렉토리에 mount ( mountPath: /etc/nginx/conf.d )

conf.d 내부의 파일들은 모두 삭제되고 key이름의 설정파일( .conf )만 존재하게 됨 = 덮어쓰기의 효과

directory(폴더)의 경우 기존 파일이 underlay됨( 덮어쓰기 ) → 원래 conf.d 내부에 존재했던 파일들이 다 날아간다.

 

방법5 : configMap with file

[ confmap으로 file을 이용 ]

apiVersion: v1
kind: Pod
metadata:
  name: nginx-configmap-vol
  labels:
    name: nginx-configmap-vol
spec:
  volumes:
  - name: config
    configMap:
      name: fortune-config2
  containers:
  - name: nginx-configmap-vol
    image: nginx:1.7.9
    volumeMounts:
    - name:  config
      **mountPath: /etc/nginx/conf.d/default.conf**
      subPath : custom-nginx-config.conf
      readOnly: true
    resources:
      limits:
        memory: "128Mi"
        cpu: "500m"
    ports:
      - containerPort: 8080

configMap을 볼륨으로 선언 → default.conf 파일에 mount ( mountPath: /etc/nginx/conf.d/default.conf )

이때 file을 일대일로 mount

  • mountPath: /etc/nginx/conf.d/default.conf ⇒ 교환대상 파일
  • subPath : custom-nginx-config.conf ⇒ 교체할 파일의 key

key값( 파일명 )을 참고하라

파일내용은 그대로 default.conf이지만 내용이 변경되어있다.

파일을 바꾸는 것X(이름), 파일의 내용을 바꾸는 것!!

file의 경우 목적파일 이외의 기존 파일의 내용이 유지됨 ( union filesystem으로 맨위에 쌓인 파일만 보이고 나머지는 숨겨진다 )

→ 원래 conf.d 내부에 존재했던 파일 위로 덮어쓰기가 된다.

→ 기존의 exampe_ssl.conf는 그대로 존재한다.

 

Secret

패스워드, 인증서와 같은 중요한 정보를 저장하는 목적으로 사용됨

base64인코딩( 원문추정가능 )만 이루어질뿐 암호화가 이루어지지는 않음