17. 클러스터 유지와 보안 트러블 슈팅 (3)
이 문서는 인프런 강의 "데브옵스를 위한 쿠버네티스 마스터"을 듣고 작성되었습니다. 최대한 요약해서 강의 내용을 최소로 하는데 목표를 두고 있어서, 더 친절하고 정확한 내용을 원하신다면 강의를 구매하시는 것을 추천드립니다. => 강의 링크
Security Context
Security Context
는 Kubernetes
클러스터의 보안을 위한 리소스 중 하나이다. 아무것도 설정되지 않은 Pod
의 컨테이너들은 모두 "root" 권한으로 실행된다. 만약 Persistent Volume
에 연결된 컨테이너가 해킹된다면? 이런 경우 외에도 수 많은 보안 위험에 노출될 수 있는데 Security Context
를 사용하면 어느 정도 보호해줄 수 있다.
Security Context
로 Pod
혹은 컨테이너 단위로 할 수 있는 것은 다음과 같다.
- 권한 상승 가능 여부
- 프로세스 "UID/GID"를 통한 오브젝트 액세스 제어
- "Linux Capability"를 활용한 커널 기능 추가
- SELinux 기능
- AppArmor 기능
- Seccomp Process 기능
먼저 다음을 한 번 만들어보자.
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
마음대로 삭제가 가능하다. 굉장히 보안에 취약한 상태라고 볼 수 있다. 이제 다음 리소스를 생성해보자
.
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 Policy
는 Pod
별로 방화벽을 설정해줄 수 있다. 쉽게 생각하면 AWS
의 Security Group
과 유사하다. 다음과 같이 3개의 식별자 조합을 통해 네트워크 트래픽을 제어할 수 있다.
- 허용되는 다른 Pod
- 허용되는 Namespace
- IP 블록
이 리소스는 Kubernetes
클러스터에 설치된 CNI
가 네트워크 플러그인을 지원해야 한다. Weavenet
은 지원하고 있고 GKE
같은 경우 클러스터를 만들 때 다음과 같이 왼쪽 탭의 "네트워크"를 클릭한다.
그 후 "Kubernetes 네트워크 정책 사용 설정" 옵션을 활성화해주고 만들어준다.
이제 다음과 같이 Pod
들을 만들고 Network Policy
를 만들어줄 것이다.
먼저 다음 리소스들을 생성해보자.
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: {}
이렇게 하면 이미지가 nginx
인 Pod
1개, Service
1개씩 들어있는 세트가 총 3개 만들어진다. 현재는 Network Policy
가 없기 때문에 모든 Pod
가 다른 Service
를 호출할 수 있다.
# kubectl exec -it <생성된 pod id> -- curl <생성된 svc name>
$ kubectl exec -it np3-d4799d44f-lfpjp -- curl np2
이제 위의 그림이 되게끔 통신을 막아보자. 다음과 같은 리소스를 생성하면 된다.
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 가이드
트러블 슈팅은 "로깅"이 매우 중요하다. 이걸 명심하자. 또한 트러블 슈팅은 크게 다음으로 나눌 수 있다.
- 클러스터 트러블 슈팅
- 애플리케이션 트러블 슈팅
보통 클러스터 수준의 트러블 슈팅은 API
가 "deprecated" 되었다던가, 인증서가 만료되었다던가하는 것 정도가 있다. 애플리케이션 트러블 슈팅은 거의 사용자가 문제를 일으키는 편이다.
애플리케이션 트러블 슈팅은 다음과 같은 특징이 있다.
- Pod, ConfigMap 등의 로직이 잘못되었는지 단위 별로 체크
- 일반적으로 이름이나 레이블링이 틀려서 문제가 생김
클러스터 트러블 슈팅은 다음과 같은 특징이 있다.
- 쿠버네티스 시스템에 대한 깊은 이해가 필요
- 일반적으로 다수 보다는 한, 두군데 장애가 존재
예를 들면, kubectl
이 접속이 안되면 kube-apiserver
를 봐야 한다든가 컨테이너 배치가 안되면 kube-scheduler
를 봐야 한다든가 깊은 이해가 필요하다. 로그를 찾을 때는 마스터 노드의 컴포넌트 로그들, journalctl
을 이용하여 kubelet
등의 서비스 로그 순으로 확인하면 좋다.