ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 17. 클러스터 유지와 보안 트러블 슈팅 (3)
    개발 스터디/데브옵스(DevOps)를 위한 쿠버네티스 마스터 2021. 9. 8. 20:49
    반응형

    이 문서는 인프런 강의 "데브옵스를 위한 쿠버네티스 마스터"을 듣고 작성되었습니다. 최대한 요약해서 강의 내용을 최소로 하는데 목표를 두고 있어서, 더 친절하고 정확한 내용을 원하신다면 강의를 구매하시는 것을 추천드립니다. => 강의 링크

    Security Context

    Security ContextKubernetes 클러스터의 보안을 위한 리소스 중 하나이다. 아무것도 설정되지 않은 Pod의 컨테이너들은 모두 "root" 권한으로 실행된다. 만약 Persistent Volume에 연결된 컨테이너가 해킹된다면? 이런 경우 외에도 수 많은 보안 위험에 노출될 수 있는데 Security Context를 사용하면 어느 정도 보호해줄 수 있다.

     

    Security ContextPod 혹은 컨테이너 단위로 할 수 있는 것은 다음과 같다.

    • 권한 상승 가능 여부
    • 프로세스 "UID/GID"를 통한 오브젝트 액세스 제어
    • "Linux Capability"를 활용한 커널 기능 추가
    • SELinux 기능
    • AppArmor 기능
    • Seccomp Process 기능

     

    먼저 다음을 한 번 만들어보자.

    src/ch17/k8s/sc-1.yaml

    apiVersion: v1
    kind: Pod
    metadata:
      name: sc-1
    spec:
      containers:
        - name: sc-1
          image: gcr.io/google-samples/node-hello:1.0

     

    위 생성된 Pod은 여지껐 만들어왔던 방식이다. 먼저 쉘에 접속해보자.

    $ kubectl exec -it sc-1 -- bash
    root@sc-1:/#

     

    여기서 id라는 명령어를 입력해보자.

    root@sc-1:/# id
    uid=0(root) gid=0(root) groups=0(root)

     

    "uid", "gid", "group"이 모두 "root" 권한으로 실행된 것을 확인할 수 있다. 이번엔 ps aux라는 명령어를 입력해본다.

    root@sc-1:/# ps aux
    USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
    0              1  0.0  0.0   4332   712 ?        Ss   10:32   0:00 /bin/sh -c node server.js
    0              6  0.0  0.5 772120 22880 ?        Sl   10:32   0:00 node server.js
    0             11  0.0  0.0  20240  3256 pts/0    Ss   10:32   0:00 bash
    0             25  0.0  0.0  17496  2128 pts/0    R+   10:38   0:00 ps aux

     

    실행되는 모든 프로세스를 확인할 수 있다. 여기서 이제 "root"라는 디렉토리를 지워보자.

    # 디렉토리 목록 확인
    root@sc-1:/# ls
    bin  boot  dev  etc  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  server.js  srv  sys  tmp  usr  var
    
    # root 디렉토리 제거
    root@sc-1:/# rm -rf root
    
    # 제거됨
    root@sc-1:/# ls
    bin  boot  dev  etc  home  lib  lib64  media  mnt  opt  proc  run  sbin  server.js  srv  sys  tmp  usr  var

     

    마음대로 삭제가 가능하다. 굉장히 보안에 취약한 상태라고 볼 수 있다. 이제 다음 리소스를 생성해보자

    .

    src/ch17/k8s/sc-2.yaml

    apiVersion: v1
    kind: Pod
    metadata:
      name: sc-2
    spec:
      securityContext:
        runAsUser: 1000
      containers:
        - name: sc-2
          image: gcr.io/google-samples/node-hello:1.0
          securityContext:
            runAsUser: 4000 # container 단위
            allowPrivilegeEscalation: false

     

    Pod는 "uid=1000, gid=root, group=root"으로 실행된다. 컨테이너의 "uid=4000"이다. 이렇게 하면 쉘에 접속은 되도 권한이 막혀 어떤 명령어로도 내부를 수정할 수 없다. Pod가 생성되면 똑같이 쉘에 접속해본다.

    $ kubectl exec -it sc-2 -- bash
    I have no name!@sc-2:/$

     

    이제 "id"를 입력해보자.

    I have no name!@sc-2:/$ id
    uid=4000 gid=0(root) groups=0(root)

     

    이제 ps aux라는 명령어를 입력해보자.

    I have no name!@sc-2:/$ ps aux
    USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
    4000           1  0.0  0.0   4332   728 ?        Ss   10:52   0:00 /bin/sh -c node server.js
    4000           8  0.0  0.5 772120 22768 ?        Sl   10:52   0:00 node server.js
    4000          13  0.0  0.0  20240  3260 pts/0    Ss   10:52   0:00 bash
    4000          20  0.0  0.0  17496  2088 pts/0    R+   10:53   0:00 ps aux

     

    이제 "uid=4000"으로 실행되는 것을 확인할 수 있다. 한 번 똑같이 "root"라는 디렉토리를 삭제해보자.

    I have no name!@sc-2:/$ rm -rf root
    rm: cannot remove 'root': Permission denied

     

    에러다. 왜냐하면 파일에 대한 권한이 "root"이기 때문이다. 프로세스 실행 권한은 "4000"이다. 그래서 권한이 낮기 때문에 삭제할 수 없다. 이런 식으로 컨테이너 혹은 Pod 단위의 보안 수준을 높일 수가 있다.

    Network Policy

    Network PolicyPod별로 방화벽을 설정해줄 수 있다. 쉽게 생각하면 AWSSecurity Group과 유사하다. 다음과 같이 3개의 식별자 조합을 통해 네트워크 트래픽을 제어할 수 있다.

    • 허용되는 다른 Pod
    • 허용되는 Namespace
    • IP 블록

    이 리소스는 Kubernetes 클러스터에 설치된 CNI가 네트워크 플러그인을 지원해야 한다. Weavenet은 지원하고 있고 GKE 같은 경우 클러스터를 만들 때 다음과 같이 왼쪽 탭의 "네트워크"를 클릭한다.

    그 후 "Kubernetes 네트워크 정책 사용 설정" 옵션을 활성화해주고 만들어준다.

    이제 다음과 같이 Pod들을 만들고 Network Policy를 만들어줄 것이다.

     

    먼저 다음 리소스들을 생성해보자.

     

    src/ch17/k8s/np-prepare.yaml

    apiVersion: v1
    kind: Service
    metadata:
      creationTimestamp: null
      labels:
        app: np1
      name: np1
    spec:
      clusterIP: None
      selector:
        app: np1
      type: ClusterIP
    status:
      loadBalancer: {}
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      creationTimestamp: null
      labels:
        app: np1
      name: np1
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: np1
      strategy: {}
      template:
        metadata:
          creationTimestamp: null
          labels:
            app: np1
        spec:
          containers:
            - image: nginx
              name: nginx
              resources: {}
    status: {}
    ---
    apiVersion: v1
    kind: Service
    metadata:
      creationTimestamp: null
      labels:
        app: np2
      name: np2
    spec:
      clusterIP: None
      selector:
        app: np2
      type: ClusterIP
    status:
      loadBalancer: {}
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      creationTimestamp: null
      labels:
        app: np2
      name: np2
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: np2
      strategy: {}
      template:
        metadata:
          creationTimestamp: null
          labels:
            app: np2
        spec:
          containers:
            - image: nginx
              name: nginx
              resources: {}
    status: {}
    ---
    apiVersion: v1
    kind: Service
    metadata:
      creationTimestamp: null
      labels:
        app: np3
      name: np3
    spec:
      clusterIP: None
      selector:
        app: np3
      type: ClusterIP
    status:
      loadBalancer: {}
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      creationTimestamp: null
      labels:
        app: np3
      name: np3
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: np3
      strategy: {}
      template:
        metadata:
          creationTimestamp: null
          labels:
            app: np3
        spec:
          containers:
            - image: nginx
              name: nginx
              resources: {}
    status: {}

     

    이렇게 하면 이미지가 nginxPod 1개, Service 1개씩 들어있는 세트가 총 3개 만들어진다. 현재는 Network Policy가 없기 때문에 모든 Pod가 다른 Service를 호출할 수 있다.

    # kubectl exec -it <생성된 pod id> -- curl <생성된 svc name>
    $ kubectl exec -it np3-d4799d44f-lfpjp -- curl np2

     

    이제 위의 그림이 되게끔 통신을 막아보자. 다음과 같은 리소스를 생성하면 된다.

     

    src/ch17/k8s/np.yaml

    apiVersion: networking.k8s.io/v1
    kind: NetworkPolicy
    metadata:
      name: np1-ingress
    spec:
      podSelector:
        matchLabels:
          app: np1
      policyTypes:
        - Ingress
      ingress:
        - from:
            - podSelector:
                matchLabels:
                  app: np2
    ---
    apiVersion: networking.k8s.io/v1
    kind: NetworkPolicy
    metadata:
      name: np2-ingress
    spec:
      podSelector:
        matchLabels:
          app: np2
      policyTypes:
        - Ingress
      ingress:
        - from:
            - podSelector:
                matchLabels:
                  app: np3
    ---
    apiVersion: networking.k8s.io/v1
    kind: NetworkPolicy
    metadata:
      name: np3-ingress
    spec:
      podSelector:
        matchLabels:
          app: np3
      policyTypes:
        - Ingress

     

    "np1"의 경우 "np2"만 들어올 수 있게 "ingress" 설정을 진행했다. "np2"는 "np3"만, "np3"는 어느 Pod도 다른 곳에 호출할 수 없게 막아두었다. 끝이다. 이제 이걸 만들고 또 호출을 하면 위에서 그린 그림과 같이 호출 관계가 형성된 것을 확인할 수 있다.

     

    현재는 Pod에서 외부로 나가는 트래픽을 제어할 필요는 없기 때문에 "egress" 설정은 건너 뛰었다. 작성요령은 비슷하다. 그리고 namespaceSelector는 네임스페이스 별로, ipBlock은 아이피 블록 별로 podSelector와 비슷하게 "ingress/egress"를 관리할 수 있다.

    Trouble Shooting 가이드

    트러블 슈팅은 "로깅"이 매우 중요하다. 이걸 명심하자. 또한 트러블 슈팅은 크게 다음으로 나눌 수 있다.

    1. 클러스터 트러블 슈팅
    2. 애플리케이션 트러블 슈팅

    보통 클러스터 수준의 트러블 슈팅은 API가 "deprecated" 되었다던가, 인증서가 만료되었다던가하는 것 정도가 있다. 애플리케이션 트러블 슈팅은 거의 사용자가 문제를 일으키는 편이다.

     

    애플리케이션 트러블 슈팅은 다음과 같은 특징이 있다.

    1. Pod, ConfigMap 등의 로직이 잘못되었는지 단위 별로 체크
    2. 일반적으로 이름이나 레이블링이 틀려서 문제가 생김

    클러스터 트러블 슈팅은 다음과 같은 특징이 있다.

    1. 쿠버네티스 시스템에 대한 깊은 이해가 필요
    2. 일반적으로 다수 보다는 한, 두군데 장애가 존재

    예를 들면, kubectl이 접속이 안되면 kube-apiserver를 봐야 한다든가 컨테이너 배치가 안되면 kube-scheduler를 봐야 한다든가 깊은 이해가 필요하다. 로그를 찾을 때는 마스터 노드의 컴포넌트 로그들, journalctl을 이용하여 kubelet등의 서비스 로그 순으로 확인하면 좋다.

Designed by Tistory.