쿠버네티스 전문가 양성과정 9주차 5일(2/17)

최수환·2023년 2월 17일
0

Kubernetes

목록 보기
41/75
post-thumbnail

네트워크

쿠버네티스 네트워크에는 서비스, 로드밸런싱, 네트워킹이 있다.

📌 만든 리소스를 삭제할때 'kubectl delete -f .'을 통해 쉽게 삭제할 수 있다 = 현재 디렉터리에 있는 파일로 만든 모든 리소스를 삭제한다

서비스

서비스의 주요 기능

  • 파드의 proxy 역할
  • LB = reverse proxy
  • service discovery
    -> pod를 생성해 ip를 확인해보면 삭제하고 다시 재생성하면 ip가 계속해서 랜덤으로 바뀐다. 만약 db를 담당하는 파드와 웹을 담당하는 파드가 있다면 웹 파드는 db파드와 연결하기 위해 ip세팅을 할 것이다. 문제는 db가 교체된다면 ip세팅을 새로 해야할 것이다. 이것은 비효율적이고, service discovery를 이용해 웹에 연결된 db의 ip를 찾아내는 것이다.

📗 서비스 개념 참조

1 . 서비스 예시

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: myapp-rs
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp-rs
  template:
    metadata:
      labels:
        app: myapp-rs
    spec:
      containers:
      - name: myapp
        image: ghcr.io/c1t1d0s7/go-myweb:alpine
        ports:
        - containerPort: 8080
        


-> 레플리카셋으로 8080포트가 열린 컨테이너를 가진 총 세개의 파드를 생성한다. 또한 selector로 rs와 파드를 연결한다.

apiVersion: v1
kind: Service
metadata:
  name: myapp-svc
spec:
  ports:
  - port: 80
    targetPort: 8080
  selector:
    app: myapp-rs


-> 80:8080으로 포트포워딩하고, 셀렉터로 위에서 생성한 파드의 레이블을 지정한다. 즉, 서비스라는 리소스와 파드 세개가 또한 느슨한 연결을 한 것이다.

  • 프록시 역할 : 클라이언트가 서비스에게 80번포트로 요청을 하면 서비스는 파드와 클라이언트 중간에서 8080으로 포트포워딩해주면서 엔드포인트의 정보를 바탕으로 파드와 연결시켜준다.

    -> curl 서비스ip로 서비스에게 요청을 보냈을때 서비스는 80포트로 들어온 요청이므로 8080으로 포트포워딩 해준다. 엔드포인트의 정보를 바탕으로 파드와 연결된 모습이다.

  • 로드밸런서 역할 : 세개의 파드가 모두 8080포트가 열려있다. 프록시 역할을 하는 서비스는 클라이언트의 요청에 대해서 파드에 연결해줄때 로드밸런싱으로 돌아가면서 연결해준다.

    -> curl 요청을 여러번 했을때 연결되는 파드가 로드밸런싱되어 계속해서 바뀐다.

  • 서비스 디스커버리 역할 : 엔드포인트는 서비스에 붙어있고 target에 정보를 가지고있다. 서비스를 생성하면서 셀렉터로 파드의 라벨을 지정하고 느슨한 연결을 통해 연결된 파드의 ip정보를 엔드포인트에 저장한다. 따라서 만약 파드가 제거되거나, scale up해서 ip가 바뀌거나, 추가되도 엔드포인트에 변경된 ip정보가 반영이된다. 그렇기에 서비스가 자동적으로 파드(어플리케이션/서비스)를 찾아낼 수 있게 된다.

    -> 현재 서비스의 엔드포인트에 세개의 파드 주소가 저장된것을 알 수 있다.

    -> 파드 하나를 제거한뒤 rs에 의해 자동적으로 하나가 더 생겼다.
    이때 변경된 ip를 자동으로 엔드포인트가 반영한 모습이다.

    -> 이번에는 rs에 scale을 통해 5개로 늘린후 엔드포인트를 확인해보면 "+ 2 more"라는 표시가 있다는 것을 확인

    -> describe로 엔드포인트를 확인해보면 추가된 두개의 파드에 ip를 반영한 모습이다.

1-1 . 가상 클라이언트 생성 및 서비스 예시

kubectl run nettool -it --image ghcr.io/c1t1d0s7/network-multitool --rm

-> 클라이언트 파드를 생성한 후 run을 통해 파드에 접속한다.
( 클라이언트 측)

-> 클라이언트가 curl로 서비스에 요청을 보내면 1번의 결과와 같은 것을 확인

-> ip가 아닌 서비스명을 입력해도 결과가 나오는 것을 확인


서비스 디스커버리하기

  • 쿠버네티스는 서비스를 찾는 두 가지 기본 모드를 지원한다. - 환경 변수와 DNS

<환경 변수>

  • 파드가 노드에서 실행될 때, kubelet은 각 활성화된 서비스에 대해 환경 변수 세트를 추가한다. {SVCNAME}SERVICE_HOST 및 {SVCNAME}_SERVICE_PORT 환경 변수가 추가되는데, 이 때 서비스 이름은 대문자로, 하이픈(-)은 언더스코어()로 변환하여 사용한다. 또한 도커 엔진의 "레거시 컨테이너 연결" 기능과 호환되는 변수(makeLinkVariables 참조)도 지원한다.
#위에서 만든 서비스와 파드에 대해서 

kubectl run nettool -it --image ghcr.io/c1t1d0s7/network-multitool --rm
#클라이언트파드 생성 후 접속, env명령어 입력


-> 위의 사진처럼 환경변수에 프록시 역할을 하는 서비스의 포트와 ip가 작성이 되어있는 것을 볼 수 있다.
💡 단점으로는 클라이언트 파드 생성전에 서비스를 만들어야 한다는 것이다. 따라서 환경변수보다는 애드-온을 사용하여 DNS를 설정하는 것이 필수 적이다.

< DNS >

  • DNS 만 사용하여 서비스의 클러스터 IP를 검색하는 경우, 위의 환경변수의 단점인 순서이슈에 대해 신경 쓸 필요가 없다.

  • CoreDNS와 같은, 클러스터-인식 DNS 서버는 새로운 서비스를 위해 쿠버네티스 API를 감시하고 각각에 대한 DNS 레코드 세트를 생성한다. 클러스터 전체에서 DNS가 활성화된 경우 모든 파드는 DNS 이름으로 서비스를 자동으로 확인할 수 있어야 한다.

kubectl get pods -n kube-system # coreDNS확인


-> kube-systecm 네임스페이스에 coreDNS가 생성되어있는 것을 알 수 있다.


서비스 퍼블리싱

  • 애플리케이션 중 일부(예: 프론트엔드)는 서비스를 클러스터 밖에 위치한 외부 IP 주소에 노출하고 싶은 경우가 있을 것이다.

1 . Node포트 타입을 사용한 예시

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: myapp-rs
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp-rs
  template:
    metadata:
      labels:
        app: myapp-rs
    spec:
      containers:
      - name: myapp
        image: ghcr.io/c1t1d0s7/go-myweb:alpine
        ports:
        - containerPort: 8080

-> rs로 파드 3개 생성

apiVersion: v1
kind: Service
metadata:
  name: myapp-svc-np
spec:
  type: NodePort # nodeport사용
  ports:
  - port: 80
    targetPort: 8080
    nodePort: 31111
  selector:
    app: myapp-rs

-> type에서 nodeport를 사용하여 노드자체에 포트를 달아주어 외부에 노출시킨다.

kubectl get svc # 생성한 서비스 확인


-> curl명령을 통해 node의 호스트ip:31111을 입력했을 때 접속되는 것을 확인
-> 클라이언트가 외부에 노출되어있는 nodeport로 접속을 하는 것이고, 이때 노드에 접속하는것이 아닌, 서비스의 클러스터ip 타입을 통해 서비스 리소스에 전달되어 서비스가 마찬가지로 프록시역할을 하면서 포트포워딩을 통해 해당 파드에 연결시켜주는 것이다.
-> 따라서 nodeport는 = nodeport + cluster ip이다

📗 cluster ip는 내부에서만 작동하는 것이고 내부의 서비스 리소스를 찾게 해주는 타입이다.

kubectl edit svc myapp-svc-np # 서비스의 포트를 실시간으로 변경 가능    


-> 다만 포트를 311로 지정했을경우 오류가 나는 것을 알 수 있는데 nodeport는 30000-32767사이에서 설정해야 한다.

2 . 로드밸런서 타입을 사용한 예시

  • node포트를 사용하는 경우 고객에게 포트 30000번대를 일일이 입력하고 들어오라는것은 비현실적이다. 이때 사용하는것이 로드밸런서 타입이다
#파드는 1번예시처럼 3개 생성했다고 가정 

apiVersion: v1
kind: Service
metadata:
  name: myapp-svc-lb
spec:
  type: LoadBalancer
  ports:
  - port: 80
    targetPort: 8080
  selector:
    app: myapp-rs

  • 외부용 ip가 존재 하는 것을 확인
  • 노드포트 31235가 존재하는 것을 확인


-> 클라이언트는 외부의 ip가 보일 것이고 외부에서 접속하기위해 해당 외부ip를 입력하고 들어올 것이다. 이 접속을 포트포워딩을 통해 노드포트 31235로 전달하고, 이때 부터 노드포트 타입과 마찬가지로 cluster ip를 통해 서비스 리소스를 찾아 서비스에 전달한다. 서비스는 마찬가지로 프록시 역할을 하면서 엔드포인트정보를 보고 8080으로 포트포워딩하면서 특정 노드의 파드에 연결을 해준다.
-> 로드밸런서 = 로드밸런서 + nodeport + cluster ip

📒 1번, 2번예시 모두 브라우저에 ip를 입력하면 curl결과와 마찬가지로 접속될 것이다.
📒 로드밸런서 타입을 실제로 구축하기 위해서는 metallb 컨트롤러가 필요하다.

3 . Externelname 타입을 사용한 예시

# 파드는 1번예시처럼 3개 생성했다고 가정

apiVersion: v1
kind: Service
metadata:
  name: example
spec:
  type: ExternalName
  externalName: example.com      

-> externalname을 설정하면 만일 외부 endpoints의 정보가 바뀌어도 kubernetes의 소스코드를 변경할 필요없이 externalname만 바꿔주면 되기에 훨씬 편리하다.


readinessProbe

  • 컨테이너가 요청을 처리할 준비가 되었는지 여부를 나타낸다. 만약 준비성 프로브(readiness probe)가 실패한다면, 엔드포인트 컨트롤러는 파드에 연관된 모든 서비스들의 엔드포인트에서 파드의 IP주소를 제거한다. 준비성 프로브의 초기 지연 이전의 기본 상태는 Failure 이다. 만약 컨테이너가 준비성 프로브를 지원하지 않는다면, 기본 상태는 Success 이다.

  • 쉽게말해 서비스의 엔드포인트에 등록여부를 결정하는 것인데,
    예를들어 서비스의 뒤에 파드가 세개있고, 각각 어플리케이션을 실행중인데, 외부에서 클라이언트가 접속할시 해당 접속은 서비스가 엔드포인트 정보를 보고 로드밸런싱을 통해 3개중 하나에 연결해준다. 이때 만약 세개 중 하나가 준비가 안되었다면, 준비가 안된 파드에 호스트를 연결한다면 어플리케이션 접속이 안될것이다.
    따라서 readinessprobe르 이용해 준비가 안된 파드가 있다면 해당 파드의 ip정보를 서비스의 엔드포인트에서 제거시킨다.

1 . readinessprobe 사용 예시

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: myapp-rs-readiness
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp-rs-readiness
  template:
    metadata:
      labels:
        app: myapp-rs-readiness
    spec:
      containers:
      - name: myapp
        image: ghcr.io/c1t1d0s7/go-myweb:alpine
        readinessProbe:
          exec:
            command:
              - ls
              - /var/ready # 존재하지 않는 디렉터리 일부로 설정
        ports:
        - containerPort: 8080

-> rs로 세개의 파드를 생성하고 readinessprobe로 존재하지 않는 디렉터리를 설정한다.

apiVersion: v1
kind: Service
metadata:
  name: myapp-svc-readiness
spec:
  ports:
  - port: 80
    targetPort: 8080
  selector:
    app: myapp-rs-readiness

-> 서비스를 생성하고 endpoints를 확인하면 ip가 존재하지 않는 것을 확인

kubectl describe 파드이름 


-> 실제로 디렉터리가 존재하지 않기 때문에 readinessprobe에 실패가 나타난 것을 확인

📘 일반적으로는 exec방식보다 httpGet방식을 사용하지만 이해를 위해 exec을 한것이다.

kubectl exec -it myapp-rs-readiness-42zkn -- sh
# 해당 파드에 쉘로 접속하는 법

kubectl exec -it myapp-rs-readiness-42zkn -- touch /var/ready
# 해당 파드에 실제로 디렉터리를 생성해놓는다


-> 파드에 실제로 디렉터리를 생성하니 endpoints 해당 파드의 ip는 등록된 것을 알 수 있다.


헤드리스 서비스

  • 헤드리스 서비스는 스테이트풀셋과 함께 사용되어 파드에 접속하기 위해 사용된다.
  • 아직 스테이트풀셋을 배우지 않았기 때문에 헤드리스 서비스가 어떻게 동작하는지만 알아 볼 것이다.
apiVersion: v1
kind: Service
metadata:
  name: myapp-svc-headless
spec:
  clusterIP: None # 헤드리스 서비스 특징
  ports:
  - port: 80
    targetPort: 8080
  selector:
    app: myapp-rs-headless


-> cluster ip가 none으로 정의된 것이 헤드리스 서비스이다.

kubectl run nettool -it --image ghcr.io/c1t1d0s7/network-multitool --rm
# 클라이언트 파드 생성 및 접속 


-> 클라이언트에서 접속하여 host에 서비스리소스의 이름을 입력했더니 원래는 서비스의 ip, 즉 cluster ip가 나와야 하지만 바로 뒤에 연결된 세개의 파드 ip가 출력되는 것을 확인

📗 헤드리스 서비스 개념 참조


ingress 컨트롤러

  • 인그레스 리소스가 작동하려면, 클러스터는 실행 중인 인그레스 컨트롤러가 반드시 필요하다.

  • ingress : 로드밸런서처럼 내부에 파드를 외부에 노출시키는 방법이다. 서비스와의 차이점은 L7영역의 통신을 담당해서 처리한다.

  • 실습에서 사용할 것은 쿠버네티스 용 NGINX 인그레스 컨트롤러이고 NGINX 웹서버(프록시로 사용)와 함께 작동한다.

📒인그레스 컨트롤러 개념 참조


ingress

  • 인그레스는 클러스터 외부에서 클러스터 내부 서비스로 HTTP와 HTTPS 경로를 노출한다. 트래픽 라우팅은 인그레스 리소스에 정의된 규칙에 의해 컨트롤된다.
  • 로드밸런서타입, 노드포트타입은 L4에서 작동하는 것이기
    때문에 호스트 IP에 포트를 입력해야만 접속이 가능했다. 하지만 ingress는 L7에서 작동하기때문에 호스트IP만 입력해도 접속이 가능하다.

  • 인그레스(L7)에서 서비스(L4) 순서로 요청이 들어온다.
  • 이때 서비스는 로드밸런서나 노드포트 타입을 사용하여야 한다.

📒 인그레스 개념 참조

1 . 호스트를 포함한 인그레스 예시

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-wildcard-host
spec:
  rules:
  - host: "foo.bar.com"
    http:
      paths:
      - pathType: Prefix
        path: "/bar"
        backend:
          service:
            name: service1
            port:
              number: 80
  - host: "*.foo.com"
    http:
      paths:
      - pathType: Prefix
        path: "/foo"
        backend:
          service:
            name: service2
            port:
              number: 80


-> 호스트를 설정할 수 있는데 호스트를 두개이상 설정한다면 순서가 중요하다
-> 만약 '별'.foo.com 호스트를 설정하고 아래에 bar.foo.com을 설정하면 위의 '별'.foo.com이 bar를 포함하는 더 큰 범위의 호스트이기 때문에 아래에 bar.foo.com 호스트에는 도착할일이 없어진다.

2 . 3개파드 생성

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: myapp-rs
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp-rs
  template:
    metadata:
      labels:
        app: myapp-rs
    spec:
      containers:
      - name: myapp
        image: ghcr.io/c1t1d0s7/go-myweb:alpine
        ports:
        - containerPort: 8080

2 -1 . 서비스 생성

apiVersion: v1
kind: Service
metadata:
  name: myapp-svc-np
spec:
  type: NodePort
  ports:
  - port: 80
    targetPort: 8080
    nodePort: 31111 # 노드포트 사용
  selector:
    app: myapp-rs

2 -2 . 인그레스 생성

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: myapp-ing
spec:
  defaultBackend: # 일치하는 서비스가 없을때 기본적으로 선택하는 서비스 설정
    service:
      name: myapp-svc-np # 생성한 서비스 연결 
      port:
        number: 80
  rules:
# - host: www.example.com 
  - http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: myapp-svc-np # 생성한 서비스 연결 
            port:
              number: 80

kubectl get ing # 생성한 인그레스 확인


-> 시간이 지나면 연결된 노드의 호스트ip를 저장한다
-> 서비스에 노드포트로 각 노드에 포트를 뚫어놨고 각 노드의 ip를 ingress가 저장해놓는다


-> ingress가 자신과 연결된 노드의 호스트ip를 가지고 있는 상태에서 클라이언트가 192.168.56.21가 입력했다고 가정하자.
해당 ip는 node1의 ip이고 node1의 nodeport에 의해 뚫려있는 31111로 접속해서 cluster ip에 의해 서비스를 찾아 8080으로 포트포워딩하면서 엔드포인트의 정보를 보고 로드밸런싱되면서 랜덤으로 파드에 연결해준다.
-> 2-2번 코드에서 주석처리한 host에서 주석을 빼면 curl명령에 hostip를 입력하지 않고 도메인(host이름)을 입력할 수 있다.
-> 예를 들어, curl http://www.example.com으로 접속이 가능하다는 것이다. 하지만 실제로 우리가 해당 도메인을 산것이 아닌 이미 존재하는 도메인이기 때문에 원하는 결과가 나오지 않는다.
📘 도메인을 route53같은데에 올려서 사용하면 curl, 브라우저에서 접속이 가능해진다.
📘 만약 브라우저가 아닌 로컬에서만 도메인입력이 가능하게 하고싶다면 --resolve옵션이나 /etc/hosts파일을 사용하면 된다.

3 . 인그레스 다중 경로 사용 예시

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web-ing
  annotations: # 주석 부분 
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - host: "goorm.com"
    http:
      paths:
      - path: /apache
        pathType: Prefix
        backend:
          service:
            name: apache-svc
            port:
              number: 80
      - path: /nginx
        pathType: Prefix
        backend:
          service:
            name: nginx-svc
            port:
              number: 80

-> 아래와 같이 curl 명령을 입력했을 때 클라이언트 입장에서는 경로를 구분해서 입력을 한것이고 ingress는 구분한 경로에 따라 경로에 맞는 서버에 도달하도록 해준다. 하지만 서버입장에서는 경로가 존재하지 않는다. 서버는 오로지 자신한테 도달한 요청의 ip만을 가져야한다. 따라서 서버에 최종적으로 도달할때는 /nginx or /apache같은 경로를 제거하고 보내야한다.

curl 192.168.56.21/apache or curl 192.168.56.21/nginx
  • 경로를 제거하기 위해 예시의 주석부분을 추가한다
  • '/' 경로로 구분한뒤 서버에 도달할때는 '/'뒤에 경로부분을 제거해서 보낸다
  1. 인그레스 다중 호스트 사용 예시
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: myapp-ing-mhost
spec:
  defaultBackend:
    service:
      name: myapp-svc-np
      port:
        number: 80
  rules:
  - host: web1.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: myapp-svc-ext-np1
            port:
              number: 80
  - host: web2.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: myapp-svc-ext-np2
            port:
              number: 80
profile
성실하게 열심히!

0개의 댓글