본문 바로가기

프로그래밍/kubernetes

CKA 시험 관련 잡다한 정리(3)

잡다한 정리(2)도 글이 너무 길어져서 3으로 넘어가도록 하겠다


강의 동영상을 통해 kubernetes security 관련 내용을 보고 있는데 개인적으로 보안..에 대해서는 개념이 꽝이어서 그런지 이해가 어렵다..ㅠㅠ..예전 커리큘럼에서는 Security 항목이 명시가 되어 있었는데 현재 커리큘럼에서는 명시가 되어있질 않아서 Security가 시험에 나오지 않을수도 있겠다..라는 생각도 들지만 요즘 세상에 비추어 보면 그건 말도 안될듯 하고..그래서 이제껏 동영상 관련 내용이 정확하지 않을수도 있다..라는 식으로 내 글에 대한 설명을 땜빵했다면 이 부분은 정말 강조해서 정확하지 않을수도 있다..라고 말하고 싶다..ㅠㅠ..(아..문득 커리큘럼에서 빠진 이유가..CKS(Certificate Kubernetes Security 자격증이 새로 생겨서 그래서 그런거 아닐까..하는 생각이 들었다)

 

일반 사용자는 자신을 증명하기 위한 Root Certicicates가 필요하고 Client 와 Server 또한 각각 자신을 증명하는 Client Certificates와 Server Certificates가 있어야 한다. Root Certificates는 인증기관에서 발급하고, Client Certificates와 Server Certificates는 각각 Client와 Server에서 발급한다.

 

public key는 파일명이 *.crt, *.pem 파일로 구성되며, private key는 파일명이 *.key, *-key.pem 파일로 이루어진다.

(ex : public key : server.crt, server.pem, client.crt, client.pem)

(ex : private key : server.key, server-key.pem, client.key, client-key.pem)

파일명이 예시로 들은 이름과 동일하게 간다는것에 촛점을 맞추지 말고 확장자와 파일명을 구성하는 패턴에 맞춰라.

 

 

Kubernetes의 통신은 TLS 기반 통신으로 이루어진다. 통신은 Client와 Server 간의 통신으로 이루어진다. Client는 kubectl 같은 프로그램이 될 수도 있고 Kubernetes 구성 요소가 될 수도 있다. 또 어떤 구성요소는 Client 일수도 있고 Server 일수도 있다(이것은 서로간의 통신 관계로 인해 구분이 되어 지는 것이다. 관련 내용을 표로 정리해보았다

 

Client Server Client File Server File
KubeCtl KUBE-APISERVER admin.crt
admin.key
apiserver.crt
apiserver.key
KUBE-SCHEDULER KUBE-APISERVER scheduler.crt
scheduler.key
apiserver.crt
apiserver.key
KUBE-CONTROLLER-MANAGER KUBE-APISERVER controller-manager.crt
controller-manager.key
apiserver.crt
apiserver.key
KUBE-PROXY KUBE-APISERVER kube-proxy.crt
kube-proxy.key
apiserver.crt
apiserver.key
KUBE-APISERVER ETCD SERVER apiserver-etcd-client.crt
apiserver-etcd-client.key
etcdserver.crt
etcdserver.key
KUBE-APISERVER KUBELET SERVER apiserver-kubelet-client.crt
apiserver-kubelet-client.key
kubelet.crt
kubelet.key
KUBELET SERVER KUBE-APISERVER kubelet-client.crt
kubelet-client.key
apiserver.crt
apiserver.key

Client -> Server 로 통신이 이루어진다. Kubectl, KUBE-SCHEDULER, KUBE-CONTROLLER-MANAGER, KUBE-PROXY는 KUBE-APISERVER와 연결을 맺어 작업을 이루는 Client 역할을 하고(이들 구성요소가 하는 일들이란게 KUBE-APISERVER를 통해서 일을 하기 때문에 그렇다) ETCD SERVER는 KUBE-APISERVER가 전달하는 내용을 저장하는 Server 역할을 하고(ETCD SERVER가 저장하는것이 현재 kubernetes cluster 정보를 저장하는 역할이기 때문에 그 정보를 전달해주는 KUBE-APISERVER와 통신이 이루어진다) KUBE-APISERVER와 KUBELET SERVER는 서로 상호간의 통신을 주고받으며 Pod의 ㄴSchedulling을 담당하게 된다.

 

통신을 하는데 있어서 동일한 파일을 사용해도 된다. 예를 들어 KUBE-APISERVER는 Server 역할을 할때 apiserver.crt와 apiserver.key를 사용하고 ETCD Server와 Client 역할로 통신할때 도표에서는 apiserver-etcd-client.crt 와 apiserver-etcd-client.key 를 사용하는 형식으로 별도의 파일을 만들어서 통신하는데 이용했지만 별도로 만들지 말고 기존 apiserver.crt와 apiserver.key를 사용해도 문제없다. 

 

이렇게 만든 인증서들을 CA(Certificate Authority, 인증기관에 등록해야 한다) kubernetes cluster는 적어도 1개 이상의 CA가 있어야 한다. CA에는 자체 인증서와 key가 있다. 일단 이것을 ca.crt와 ca.key라고 명명하자.


인증서를 생성하는 내용에 대해서는 TLS Certificate Creation 을 보자. 강좌 동영상의 화면을 캡춰해서 설명을 해주셨다. 다만 링크된 글에서는 없는 내용에 대해서만 추가하겠다.

 

인증서를 이렇게 만들었으면 이 인증서를 이용해서 사용자 인증을 하게 되는데 2가지의 방법을 사용할 수 있다.

예를 들어 다음의 예는 kube-apiserver를 통해 cluster 정보를 가져오는 과정에서 사용자 인증을 하는 방법이다.

curl https://kube-apiserver:6443/api/v1/pods --key admin.key --cert admin.crt --cacert ca.crt

또 다른 방법은 사용자 인증과 관련된 정보를 넣은 ConfigMap을 만들어서 이를 이용하는 방법이다.

apiVersion: v1
clusters:
- cluster:
    certificate-authority: ca.crt
    server: https://kube-apiserver:6443
  name: kubernetes  
kind: Config
users:
- name: kubernetes-admin
  user:
    client-certificate: admin.crt
    client.key: admin.key

CSR(Certificate Signing Request) Object 만드는 순서

 

1. 먼저 key를 만든다

opnssl genrsa -out akshay.key 2048

2. key를 이용하여 csr 파일을 만든다

opnssl req -new -key akshay.key -subj "/CN=akshay" -out akshay.csr

3. 생성된 csr 파일 내용을 base64 Encoding 한다.

cat akshay.csr | base64 | tr -d "\n"

4. 다음의 yaml 파일로 CSR Object를 제작한다.

이때 spec.request 항목엔 3번에서 만든 base64로 encoding 한 CSR 파일 내용을 넣는다

(강좌 동영상의 문서는 kubernetes 1.18을 기반으로 만들어진 yaml 문서이다. kubernetes 1.19 부터는 이 yaml 문서 구조가 바뀌어서 바뀐 문서 구조로 적용했다. 해당 내용에 대해서는 아래에서 좀더 다루도록 하겠다)

apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
  name: akshay
spec:
  signerName: kubernetes.io/kube-apiserver-client
  groups:
  - system:authenticated
  usages:
  - digital signature
  - key encipherment
  - client auth
  request: 3번에서 만든 csr 파일 내용을 base64로 인코딩한 문자열
  (3번에서 나온 결과물은 여러줄의 문자열로 나올테지만 여기서 그 문자열을 사용할땐 
  여러줄이 아닌 1줄로 쭉 이어붙여서 만든뒤 넣는다)

5. kubectl apply -f 4번 yaml 을 실행하여 CSR 객체를 만든다.

 

5번까지의 과정을 통해 kubernetes에 CSR 객체를 만든다. 현재까지는 승인받기를 대기하는 상황이기 때문에 kubectl get csr 명령을 실행하여 대상이 되는 CSR 객체의 상태를 보면 Pending으로 되어 있음을 알 수 있다. kubernetes 관리자는 이렇게 대기상태인 CSR 객체를 확인하여 이를 승인하거나 또는 거절할 수 있다.

 

CSR 객체를 승인할때는 approve를 사용하고 거절할때는 approve 대신 deny를 사용한다. approve/deny 다음엔 승인하거나 거절할 csr 객체 이름을 넣어준다

kubectl certificate approve akshay

이렇게 적용을 한뒤 CSR 객체를 조회해보면 다음과 같이 나온다

CSR 객체 생성 및 승인 후의 결과

그럼 위에서 잠깐 언급했던 kubernetes 1.19 버전부터 적용된 변화에 대해 말해보겠다. CSR 생성 API가 기존의 beta버전에서 정식 버전으로 업그레이드 됨에 따라 yaml 파일에서 apiVersion 항목이 certificates.k8s.io/v1 을 사용하게 되었다. 

그리고 spec.signerName 항목이 추가되었는데 이 항목은 인증서에 서명하게 될 서명자를 나타낸다. 누가 서명하는지에 대한 언급을 해 주는 것이다. 서명자는 서명자가 허용할 수 있는 동작(usages)이 정해져 있기땜에 서명자에게 서명을 받길 원할때 CSR에 서명자가 허용할 수 있는 동작을 spec.usages 항목에 넣어야 한다. 만약 허용하지 않는 항목을 넣을 경우 서명을 허용해도 최종적으로는 실패한 것(서명자에 대한 동작을 틀리게 설정하면 kubectl certificate approve 명령을 통해 생성된 CSR 객체를 승인해줘도 상태값이 Approved,Failed로 나온다)으로 나오게 된다. 서명자의 명칭과 해당 서명자에게 허용된 동작은 다음과 같이 정리된다

 

서명자(Signer) 설명 동작(usages)
kubernetes.io/kube-apiserver-client API Server에서 클라이언트 인증서로 사용할 클라이언트 인증서에 서명한다.
kube-controll-manager에 의해 자동승인되지 않는다
["digital signature", "key encipherment", "client auth"]
이 중 client auth는 필수로 들어가야 한다
kubernetes.io/kube-apiserver-client-kubelet API Server에서 클라이언트 인증서로 사용할 클라이언트 인증서에 서명한다.
kube-controll-manager에 의해 자동승인될 수도 있다
["key encipherment", "digital signature", "client auth"]
kubernetes.io/kubelet-serving API 서버에서 유효한 kubelet 제공 인증서로 인정되는 인증서를 제공하지만 다른 보장은 없다. 
kube-controller-manager에 의해 자동 승인되지 않는다.
["key encipherment", "digital signature", "server auth"]
kubernetes.io/legacy-unknown 신뢰에 대한 보장이 전혀 없다.
타사 버전의 kubernetes(ex : Redhat의 OpenShift 같은 kubernetes 기반의 솔루션)에서는 이 서명자가 서명한 인증서를 인정할 수도 있다.
안정적인 CertificateSigningRequest API는 signerName을 kubernetes.io/legacy-unknown로 설정할 수 없다.
kube-controller-manager에 의해 자동 승인되지 않는다.
아무거나 다 들어갈 수 있다

위에서 API를 통해 사용자 인증을 거쳐 Resource를 조회하는 것은 다음과 같이 한다고 했었다

curl https://kube-apiserver:6443/api/v1/pods --key admin.key --cert admin.crt --cacert ca.crt

이를 kubectl로 하게 되면 다음과 같다.

kubectl get pods 
--server kube-apiserver:6443 
--client-key admin.key 
--client-certificate admin.crt 
--certificate-authority ca.crt

그러나 매번 이런 옵션을 입력하는것은 불편하기 때문에 config란 파일을 만들어서 그 파일 안에 다음과 같이 넣는다.

--server kube-apiserver:6443 
--client-key admin.key 
--client-certificate admin.crt 
--certificate-authority ca.crt

그리고 kubectl을 아래와 같이 실행한다

kubectl get pods --kubeconfig config

이런 config 파일은 사용자 home directory에 숨김 디렉토리로 kube라고 만든 뒤 파일명을 config로 해서 kube 디렉토리에 넣는 것이 default 설정이다. 그래서 /사용자 home 디렉토리/.kube/config 파일을 만들어서 kubectl 관련 설정 옵션들을 넣어두면 해당 옵션들이 kubectl에 자동으로 적용된다. 이러한 작업을 했으면 위의 kubectl 명령이 아래와 같이 단순해진다.

kubectl get pods

이러한 config 파일은 yaml 파일 구조를 가지고 있으며 크게 3가지 영역으로 구분되어 있다

apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: LS0tLS1CRUdJTi...
    server: https://192.168.101.11:6443
  name: kubernetes
contexts:
- context:
    cluster: kubernetes
    user: kubernetes-admin
  name: kubernetes-admin@kubernetes
current-context: kubernetes-admin@kubernetes
kind: Config
preferences: {}
users:
- name: kubernetes-admin
  user:
    client-certificate-data: LS0tLS1CRUdJTi...
    client-key-data: LS0tLS1CRUdJTi...

이 yaml 파일을 보면 3가지 영역에 대한 확인이 가능한데 clusters, contexts, users 이 3가지 내용을 저장하고 있는 것을 확인할 수 있다. clusters에 저장되는 내용은 내가 접속하고자 하는 kubernetes cluster에 대한 정보가 들어가게 된다. users는 내가 kubernetes cluster를 접속할때 사용할 계정 정보들이 들어가게 된다. 마지막으로 contexts는 clusters와 users를 맺어주는 역할을 하게 된다. 즉 어떤 kubernetes cluster에 어떤 계정정보를 사용해서 접속할 것인지를 contexts 에서 설정하게 되는 것이다. 이를 비추어 생각해보면 kubectl을 사용할때 사용했던 옵션들이 이 3가지 영역에 적용되는 것이다. 즉 --server kube-apiserver:6443 는 clusters에, --client-key admin.key, --client-certificate admin.crt, --certificate-authority ca.crt 는 users에 넣게 되는 것이다. clusters, contexts, users 에 들어가는 값의 형태가 배열이기 때문에 각 항목별로 여러개의 값이 들어갈수 있게 된다. 그리고 kubectl에서 default로 사용되는 context는 current-context 항목에서 설정하게 된다.

아래의 명령어를 실행하면 config 파일을 볼 수 있다. 단 BASE64 로 인코딩된 인증 관련 값들은 다른 내용으로 대체되어져서 보여준다

kubectl config view
# 특정 config 파일의 내용을 볼때는 다음과 같이 한다
kubectl config view --kubeconfig=my-custom-config

kubectl 명령어 사용시 default context를 변경하고자 할때는 다음과 같이 한다

# default context를 prod-user@production으로 설정하고자 할때는 다음과 같이 한다
kubectl config use-context prod-user@production

kubectl로 context 사용시 특정 namespace를 이용하고자 할때는 namespace 항목을 설정해주면 된다

apiVersion: v1
clusters:
...
contexts:
- context:
    cluster: kubernetes
    user: kubernetes-admin
  name: kubernetes-admin@kubernetes
  namespace: finance
current-context: kubernetes-admin@kubernetes
kind: Config
preferences: {}
users:
...

이렇게 contexts.context.namespace 항목에 finance를 설정해줌으로써 kubernetes-admin@kubernetes context 이용시 finance namespace를 기본 namespace로 사용하게 된다.


kubernetes cluster를 이용하는 사용자 계층은 다양하다. kubernetes cluster 관리자가 있을수 있고, 프로그램 개발자가 있으며 심지어 프로그램 개발자가 개발한 프로그램 조차 kubernetes cluster의 사용자가 될 수 있다. 그러면 이러한 다양한 성격의 사용자들이 kubernetes cluster에서 제공되는 기능들을 모두 사용할수 있게 해야 할까? 아니다. kubernetes cluster 관리자는 kubernetes cluster 자체를 관리해야 하는 사람이기에 모든 기능을 사용할수 있어야 한다. 그러나 프로그램 개발자는 프로그램 개발 프로세스에 있어서 필요한 기능만 사용하면 된다(개발자가 만든 프로그램을 배포한다고 가정해보면 배포하고자 하는 프로그램이 들어있는 이미지를 이용해 Pod을 생성하고 삭제하는 기능은 필요할수 있다. 그러나 Node를 생성하거나 삭제하는 기능은 프로그램 개발과는 무관한 기능이기 때문에 이 기능들을 이용할 필요는 없다.) 개발자가 만든 프로그램은 Pod 을 생성하거나 삭제하는 기능을 이용할 필요는 없다. 이러한 점 때문에 Authorization이 필요하게 된 배경이다.

 

Authorization Mode에는 4가지 Mode가 제공된다

- Node : 사용자가 kube-apiserver를 통해 어떤 기능을 요청하게 되면 kube-apiserver는 kubelet에게 이러한 요청을 전달해주고 kubelet은 요청된 기능에 대한 결과물을 kube-apiserver에 전달하여 사용자가 그 결과를 받아보게 된다. 이 과정에서 node authorizer 라 알려진 권한부여자가 이러한 일련의 과정들을 통제하게 된다. 즉 node authorizer가 사용자의 인증서를 보고 그에 따라 요청한 기능을 사용하는 것을 허용할지 말지를 결정하는 구조이다.

- ABAC : 어떤 사용자가 어느곳에 있는 자원을 이용할 수 있다..라는 내용을 정해진 json format에 맞춰 이를 저장하여 관리한다. 이러한 정책이 변경될 경우 저장된 내용을 수정한뒤 kube-apiserver를 재시작해야 반영되기때문에 이용에 불편한 부분이 있다

- RBAC : 사용자가 할 수 있는 기능들의 1개 이상의 묶음을 1개의 Role로 정의한뒤 해당 Role을 사용자와 Mapping 시켜서 이를 기반으로 기능의 이용 여부를 판단하는 구조이다. 가장 많이 쓰이는 구조

- Webhook : 어떤 사용자가 자원을 이용할 수 있는지의 여부를 kubernetes가 판단하는게 아니라 제 3자(Third Party Open Policy Agent)가 판단해서 이를 kubernetes에 알려주는 형태로 구현할때 사용되는 구조. 서로 주고 받는 데이터는 kubernetes에서 정해져있는 yaml 파일 구조에 따라 데이터를 설정하여 전송해준다.

 

이 외에 AlwaysAllow와 AlwaysDeny Mode가 있는데 AlwaysAllow는 어떠한 Authorization Check가 없이 모든 기능을 허용하는 Mode이고 AlwaysDeny는 어떠한 Authorization Check가 없이 모든 기능을 허용하지 않는 Mode 이다.

 

이러한 Mode는 kube-apiserver에서 설정하게 된다. 다음의 내용은 /etc/kubernetes/manifests 디렉토리에 있는 kube-apiserver.yaml 파일에서 spec 부분만 별도로 적어놓은 내용이다

spec:
  containers:
  - command:
    - kube-apiserver
    - --advertise-address=192.168.101.11
    - --allow-privileged=true
    - --authorization-mode=Node,RBAC
    - --client-ca-file=/etc/kubernetes/pki/ca.crt
    - --enable-admission-plugins=NodeRestriction
    - --enable-bootstrap-token-auth=true
    - --etcd-cafile=/etc/kubernetes/pki/etcd/ca.crt
    - --etcd-certfile=/etc/kubernetes/pki/apiserver-etcd-client.crt
    - --etcd-keyfile=/etc/kubernetes/pki/apiserver-etcd-client.key
    - --etcd-servers=https://127.0.0.1:2379
    - --insecure-port=0
    - --kubelet-client-certificate=/etc/kubernetes/pki/apiserver-kubelet-client.crt
    - --kubelet-client-key=/etc/kubernetes/pki/apiserver-kubelet-client.key
    - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
    - --proxy-client-cert-file=/etc/kubernetes/pki/front-proxy-client.crt
    - --proxy-client-key-file=/etc/kubernetes/pki/front-proxy-client.key
    - --requestheader-allowed-names=front-proxy-client
    - --requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt
    - --requestheader-extra-headers-prefix=X-Remote-Extra-
    - --requestheader-group-headers=X-Remote-Group
    - --requestheader-username-headers=X-Remote-User
    - --secure-port=6443
    - --service-account-issuer=https://kubernetes.default.svc.cluster.local
    - --service-account-key-file=/etc/kubernetes/pki/sa.pub
    - --service-account-signing-key-file=/etc/kubernetes/pki/sa.key
    - --service-cluster-ip-range=10.96.0.0/12
    - --tls-cert-file=/etc/kubernetes/pki/apiserver.crt
    - --tls-private-key-file=/etc/kubernetes/pki/apiserver.key
    image: k8s.gcr.io/kube-apiserver:v1.20.0

위의 yaml 내용을 보면 --authorization-mode=Node,RBAC 이렇게 적혀 있는 부분을 발견할 수 있을 것이다. --authorization-mode 옵션에 적용할 1개 이상의 Mode를 설정해주면 된다(2개 이상 적용해야 할 경우 ,를 구분자로 해서 하나의 문자열로 쭉 이어서 넣어주면 된다) 2개 이상의 Mode를 적용할 경우 어떤 Mode를 먼저 적용시킬지를 결정시킬수 있는데 적용시키고자 하는 순서에 따라 써주면된다. 위의 예와 같이 Node,RBAC 로 썼으면 처음엔 Node Mode로 Authorization을 판단하고 여기서 이용할수 없다고 판단하면 다시 RBAC Mode도 적용하여 이용할수 있는지 판단한다. 즉 정리하자면 순서대로 적용하되 하나의 Mode 만 만족하면 이용할 수 있다.(모든 모드에서 이용할 수 없다고 판단하면 이용할 수 없게 된다) 그리고 만족되는 Mode가 있게 되면 다음에 판단해야 할 Mode가 설정되어 있을 지라도 이를 사용하지 않고 이용할수 있다고 알려주게 된다(ex : Node,RBAC,Webhook 이렇게 3개의 Mode를 쓴다고 가정할 경우 Node에서 불허판정나고, RBAC에서 허가 판정이 났으면 Webhook Mode에서의 허가여부를 확인하지 않고 그냥 결과를 허가판정으로 준다는 의미) 


RBAC는 먼저 다음과 같은 Role 객체를 만든다

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: developer
rules:
- apiGroups: [""]
  resources: ["pod"]
  verbs: ["list", "get", "create", "update", "delete"]

apiGroups에서 대상이 되는 Resource가 속한 API Group를 설정해준다. 이 부분을 ""로 설정하면 Pod, Servce 등이 포함된 Core API Group을 의미한다(Core API Group은 특별한 API Group 명칭이 없다. kubectl api-resources를 실행해보면 APIVERSION을 보여주는데 특정 API Group에 속한 것이면 API Group 이름/APIVersion 형태로 보여주게 된다. API Group 이름 없이 그냥 APIVersion만 보여주는 것들은 Core API Group이며 그런 연유로 Core API Group이 ""로 표기가 되는 것이기도 하다) resources 항목은 생성하는 Role의 대상이 되는 Resource를 1개 이상 지정한다. Verbs는 resource 항목에 설정한 Resource들을 대상으로 어떤 작업을 할 수 있는 것인지를 정의하는 것으로 1개 이상 지정하며 list(목록조회), get(상세조회), create(생성), update(수정), delete(삭제)를 뜻한다. 또 rules 항목의 값이 배열이기 때문에 apiGroups, resources, verbs 조합으로 이루어진 rule을 1개 이상 등록할 수 있다.

 

이렇게 만든 Role과 이 Role을 사용할 사용자를 엮어주는 Resource로 RoleBinding Resource를 아래와 같이 생성해준다. 

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: devuser-developer-binding
subjects:
- kind: User
  name: dev-user
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: developer
  apiGroup: rbac.authorization.k8s.io

현재 자신이 로그인 한 계정을 통해서 특정 작업이 가능한지의 여부를 다음과 같은 명령어로 확인할 수 있다.

# 현재 로그인 한 계정으로 deployments를 생성할 수 있는지를 확인한다
kubectl auth can-i create deployments
# 현재 로그인 한 계정으로 node를 삭제할 수 있는지를 확인한다.
kubectl auth can-i delete nodes

현재 로그인 한 계정이 아닌 다른 계정을 대상으로 확인하고자 할 때는 아래와 같이 --as 옵션을 사용한다

# dev-user 계정으로 deployments를 생성할 수 있는지를 확인한다
kubectl auth can-i create deployments --as dev-user
# dev-user 계정으로 node를 삭제할 수 있는지를 확인한다.
kubectl auth can-i delete nodes --as dev-user
# dev-user 계정으로 test namespace에서 pod을 생성할 수 있는지를 확인한다
kubectl auth can-i create pods --as dev-user --namespace test

Resource의 특정 이름을 가지고 Role을 부여할 수도 있다. 예를 들어 Pod의 이름이 blue와 orange일 경우에만 적용되는 Role을 만든다면 다음과 같이 만든다

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: developer
rules:
- apiGroups: [""]
  resources: ["pod"]
  verbs: ["get", "create", "update"]
  resourceNames: ["blue", "orange"]

Kubernetes Resource 들은 Namespace 범위에서 묶이는 Resource(ex : pods, replicasets, deployments 등) 가 있지만Namespace 범위에서 묶일수 없는 Resouce도 있다. 예를 들어 Node의 경우는 특정 Namespace에 종속되는 Resource가 아니라 kubernetes cluster 레벨에서 종속되는 Resource이기 때문이다. 이렇게 Kubernetes Resource들 중에서 Cluster레벨에 종속되는 Resource들이 존재하는데 이러한 자원을 조회하는 방법은 kubectl로 다음과 같이 조회하면 된다.

# namespace에 종속되는 Resource 종류를 조회
kubectl api-resources --namespaced=true
# cluster에 종속되는 Resource 종류를 조회
kubectl api-resources --namespaced=false

이렇게 Cluster Resource에 대한 Role을 ClusterRole이라 하며 ClusterRole과 사용자를 묶은 것을 ClusterRoleBinding이라 한다. ClusterRole의 yaml 파일 구조는 기존 Role 구조와 동일하다. 대신 Kind 항목 값을 ClusterRole 로 설정하는 차이뿐이 없다(그래서 yaml 파일 예시는 생략) ClusterRoleBinding의 yaml 파일 구조 또한 기존 RoleBinding yaml 파일 구조와 같다. 다만 kind 항목 값을 ClusterRoleBinding 으로 설정하는 차이뿐이 없다(그래서 yaml 파일 예시 생략)

 

그러나 ClusterRole을 설정하는 과정에서 대상이 되는 Resource를 Cluster Resource가 아닌 Namespace Resource(ex : pods, replicasets, deployments 등)를 대상으로 설정하는 경우가 있다. 이럴 경우 모든 namespace를 대상으로 Role을 설정한다고 보면 된다.(Role이나 RoleBinding의 경우 특정 namespace를 대상으로 적용하기 때문에 metadata.namespace 항목을 설정해야 한다. 그러나 ClusterRole이나 ClusterRoleBinding의 경우 Cluster 레벨에서 적용되기 때문에 모든 namespace를 범위로 삼는다고 보면 된다. 그런 관점에서 pod이나 replicasets 같은 namespace 기반 resource를 대상으로 clusterrole을 만들게 되면 이건 특정 namespace가 아닌 모든 namespace를 대상으로 한다고 보면 된다)


Docker 이미지에 대한 주소 다음의 형태를 지닌다

 

docker.io/nginx/nginx

Docker 이미지가 저장되어 있는 Registry(docker.io) / 이미지를 배포하는 사용자 계정(nginx) / Image 저장소(nginx)

그래서 goole registry에 있는 docker 이미지를 사용할땐 gcr.io/kubernetes-e2e-test-images/dnsutils 요런 형태로 주소를 가지게 된다

 

public registry에서 docker image를 서비스할 경우 누구나 접근이 가능하기 때문에 보안에 문제가 있다.

그래서 docker hub나 각종 cloud service가 제공해주는 private registry를 통해서 docker image를 제공해주는 것이 바람직하다

private registry에서 이미지를 pull하거나 push 하기 위해선 해당 private registry에 대한 로그인 정보가 필요하다.

 

kubernetes 에서도 private registry에 보관되어 있는 이미지를 다운로드 받아 사용할땐 이러한 로그인 정보가 필요하다. kubernetes 에서는 이러한 로그인 정보를 secrets 에 만들어서 사용한다.

kubectl create secret docker-registry regcred \
--docker-server=private-registry.io \
--docker-username=registry-user \
--docker-password=registry-password \
--docker-email=registry-user@org.com

그리고 이렇게 만든 secrets를 pod을 생성하는 yaml에서 spec.imagePullSecrets 항목에 설정해준다

apiVersion: v1
kind: Pod
metadata:
  name: nginx-pod
specs:
  containers:
  - name: nginx
    image: private-registry.io/apps/internal-app
  imagePullSecrets:
  - name: regcred

kubernetes의 security context는 kubernetes의 Pod이나 컨테이너에 대한 접근 제어 설정(Access Control Setting)이나, 특수 권한 (Privilege)를 설정하는 기능을 제공한다. 이 Security Context는 Pod에서 설정하거나 또는 Pod 안에서 실행되는 container에서 설정할 수 있다. Pod level에서 설정하면 Pod에서 실행중인 모든 container에 적용되며 Pod과 Container 모두에게 설정하면 container 설정값으로 security context가 적용된다(Pod에서 설정한 값을 override 한다)

아래의 yaml 파일은 SecurityContext를 Pod Level에서 설정한 것이다

apiVersion: v1
kind: Pod
metadata:
  name: web-pod
specs:
  securityContext:
    runAsUser: 1000
  containers:
  - name: ubuntu
    image: ubuntu
    command: ["sleep", "3600"]

아래의 yaml 파일은 SecurityContext를 container Level 에서 설정한 것이다

apiVersion: v1
kind: Pod
metadata:
  name: web-pod
specs:
  containers:
  - name: ubuntu
    image: ubuntu
    command: ["sleep", "3600"]
    securityContext:
      runAsUser: 1000
      capablities:
        add: ["MAC_ADMIN"]

Pod을 접근할때 Network Policies(네트워크 정책)을 설정할 수 있다. 예를 들어 다음과 같다

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: db-policy
specs:
  podSelector:
    matchLabels:
      role: db
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          name: api-pod
    ports:
    - protocol: TCP
      port: 3306

해석을 하자면 Label이 name: api-pod 인 Pod이 Label이 role: db인 Pod으로 통신할때(role: db인 Pod 입장에서 보면 Ingress 통신임) TCP 프로토콜 3306 Port를 사용한다는 의미이다.

이러한 Network Policies 는 kube-router, Calico, Romana, Weave-net이 지원하며 flannel은 지원하지 않는다. 지원하지 않는다고 해서 NetworkPolicy를 만들지 못하는것은 아니다. 다만 에러 메시지로 NetworkPolicy를 지원하지 않는다고 나오게 된다.

 

예를 들어 API Pod에서 DB Pod으로 데이터가 전달되는 통신을 설정할때

DB Pod 입장에서는 통신이 나를 향해 들어오는 것이기 때문에 ingress가 된다.

API Pod 입장에서는 나에게서 통신이 나가기 때문에 egress가 된다

DB Pod 입장에서 Network Policies를 설정한다면 

API Pod -> DB Pod으로의 Ingress를 만들어야 하는 것이다.

그래서 위의 yaml을 보면 spec.policyTypes를 Ingress로 설정하면서 spec.podSelector.matchLabels에는 DB Pod의 label을 설정하는 것이고 spec.ingress.from.podSelector.matchLabels에는 API Pod의 label을 설정하게 되는것이다. 

 

통신은 일단 한 방향으로 설정이 되면 (ex : API Pod -> DB Pod) 그 결과는 설정된 통로를 사용해서 그대로 전달되기 때문에 따로 반대방향에 대한 정책을 만들 필요가 없다.(API Pod -> DB Pod으로 연결이 설정된 상태에서 API Pod이 데이터를 조회하기 위해 DB Pod에 질의문을 던지는 작업을 했다면 그 결과도 동일한 연결을 이용해서 return 해주기 때문에 같은 통로를 사용하게 되므로 return 하는 부분에 대한 정책을 따로 만들 필요는 없다) 그러나 DB Pod이 API Pod에 어떤 요청을 하기 위해 별도의 네트워크 연결이 필요하다면 DB Pod 입장에서는 API Pod으로 향하는 egress를 만들어야 한다.(API Pod -> DB Pod 3306 포트를 연결하는 것과 DB Pod -> API Pod 3306 포트를 연결하는것은 별개의 네트워크 통신임을 뜻하기 때문이다)

 

아래의 yaml은 특정 namespace에 해당되는 Pod에서 들어오는 통신을 설정하는 내용이다(apiVersion, kind, metadata 항목은 생략)

specs:
  podSelector:
    matchLabels:
      role: db    
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          name: api-pod
      namespaceSelector:
        matchLabels:
          name: prod
    - ipBlock:
        cidr: 192.168.5.10/32
    ports:
    - protocol: TCP
      port: 3306

이렇게 설정하면 namespace가 prod 이고 label이 name: api-pod 인 Pod만 3306번 포트로의 통신을 받아들이겠다는 의미이다. 만약 podSelector 항목을 없애고 namespaceSelector 항목만 남겨두었다면 prod namespace에 있는 모든 Pod의 3306번 포트로의 통신을 받아들이겠다는 의미가 된다.

specs.ingress.from.ipBlock은 Kubernetes Cluster에 등록된 Resource가 아닌 Machine(예를 들어 kubernetes cluster에 등록되어 있지 않은 message server 같은 경우)이 해당 ingress 가 적용되는 Pod와 통신해야 하는 경우(즉 message server -> ingress 설정 Pod 으로 통신해야 하는 경우)에 해당 machine의 ip를 적어줌으로써 ingress 통신이 가능하게끔 해준다.

 

egress 설정도 마찬가지이다. 아래는 egress 까지 설정한 yaml이다.

specs:
  podSelector:
    matchLabels:
      role: db    
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          name: api-pod
      namespaceSelector:
        matchLabels:
          name: prod
    ports:
    - protocol: TCP
      port: 3306
  engress:
  - to: 
    - ipBlock:
        cidr: 192.168.5.10/32
    ports:
    - protocol: TCP
      port: 80

egress 도 ingress와 기본 구성은 같다. 다만 ingress에서는 from 이었지만 egress에서는 to가 된다(그럴수밖에 없는것이 egress는 egress가 설정되는 Pod이 특정 Pod이나 kubernetes cluster에 등록되지 않은 machine에 접속할때의 설정이므이기 때문이다) 위의 예를 빌어서 설정 설명을 하자면 Label이 role: db인 Pod이 192.168.5.10번 ip를 가지고 있는 kubernetes cluster에 등록되어 있지 않은 machine의 80포트 접속을 허용한다는 의미가 된다.


NetworkPolicy 관련 연습문제를 풀다가 기록해둬야 할 것이 있어서 정리해본다. 샘플 연습문제는 4개의 Pod이 있고 해당 Pod에는 각각 Service가 연결되어 있었다. 이중 DB 역할을 하는 Pod은 외부와의 연결이 안되게끔 할려고 ClusterIP 형태의 Service와 연결이 되어 있었고 나머지 3개의 Pod은 NodePort 형식의 Service와 연결되어 있었다. 연습문제는 하나의 Pod에서 서비스되는 화면이 브라우저에 나오고 다른 Pod과 연결된 Service 이름과 Port 번호를 입력한뒤 버튼을 클릭하면 입력한 Pod과 Port 번호로 연결이 이루어지는지 시도하여 그 결과를 보여주는 것이었다. NodePort 타입의 서비스여서 외부에서 연결할려면 임의의 번호로 만들어진 Port 번호를 넣었어야 한다고 생각했는데 그러지 않고  관련 서비스이름에 서비스와 연결된 Pod의 내부포트 번호를 입력해도 연결이 되길래 왜 그런가 생각했다. 근데 생각해보니 NodePort를 만들면 Pod들간의 내부 연결에 사용되는 ClusterIP가 같이 만들어진다는 점을 잊고 있었다. 예를 들어 NodePort Service 이름이 internal-service 이고 NodePort로 만들어질때 사용되는 Port가 8080:30082 라고 가정하자. 이 서비스는 외부에서는 Node IP주소:30082 로도 연결되지만 같이 만들어지는 ClusterIP 때문에 Pod 안에서 연결을 할때는 insernal-service:8080 으로도 연결이 되는 것이다. 이 점을 기록해두어야 할 듯 하여 정리해봤다.

복습하다보니 또 정리할게 생겨서 적어놓는다. NetworkPolicy 연습문제를 풀다보니 Egress 형태의 Network Policy를 만들일이 있었다. 이 문제를 풀기 전에는 internal Pod 에서 보여주는 화면에서 payroll-servce:8080을 통해 payroll Pod에 접근할 수 있었는데 이 문제를 풀고나니 이게 접근이 되질 않았다. 그래서 정답으로 들어있는 파일 내용과 내가 문제를 풀은 내용을 비교해보니 spec.egress 에 ports 항목을 넣고 거기에 53번 포트로 UDP와 TCP 프로토콜을 사용하는 포트 정보를 넣어야되었다.  말로 설명해서는 이해가 안될듯 하니 정답인 yaml 파일 내용을 보여주도록 하겠다.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: internal-policy
  namespace: default
spec:
  podSelector:
    matchLabels:
      name: internal
  policyTypes:
  - Egress
  egress:
  - to:
    - podSelector:
        matchLabels:
          name: mysql
    ports:
    - protocol: TCP
      port: 3306

  - to:
    - podSelector:
        matchLabels:
          name: payroll
    ports:
    - protocol: TCP
      port: 8080

  - ports:
    - port: 53
      protocol: UDP
    - port: 53
      protocol: TCP

여기서 보면 internal Pod에서 mysql Pod과 payroll Pod을 각각 TCP 3306 포트와 8080 포트로 접속하는 것을 허용하고 있다. 그러나 이렇게 하면 한가지 문제점이 발생한다. 여기까지만 하면 mysql Pod의 IP와 payroll Pod의 IP를 이용한 통신은 가능할수는 있겠으나 이 2개의 Pod과 연결된 Service 이름을 이용한 통신은 하질 못하게 된다. 왜냐면 Service 이름을 이용해 통신할려면 kubernetes cluster 의 dns 서버 역할을 해주는 coredns Pod과 통신을 해야 하는데 egress 설정에서 이와 같은 설정이 없기 때문이다. 그래서 이를 설정해 주기 위해 모든 Pod을 대상으로 TCP 53 포트와 UDP 53 포트를 설정하는 작업을 해준것이다.(53번 port와의 통신을 허용하기 위해 사용한 ports 를 보면 to가 없이 ports 만 사용했는데 이렇게 to를 생략하면 모든 Pod을 대상으로 이 통신 규칙을 적용한다는 의미가 된다)