Kubernetes - Admission Controller
kubectl와 RBAC
kubectl의 명령어로 파드를 생성하고자 하면 kube-apiserver로 전달된 이후 파드가 생성되고 최종적으로 해당 파드 정보가 ETCD 데이터베이스에 저장된다. 지금까지 간단한 명령어만으로 이런 일련의 작업을 한 번에 해온 것이다.
그렇다면 해당 작업을 간단히 처리해주는 kubectl은 어떤 식으로 동작하는 걸까?
다음을 보자. 순서대로 설명해보도록 하겠다.
kubectl -> Authenticaion -> Authorization -> Admission Controller -> Create Pod
- kubectl로 kube-apiserver에 명령을 보낸다.
- apiserver에서 인증(Authentication) 절차를 거치게 되는데 이는 보통 인증서로 이뤄진다. 이 과정에서 요청을 보낸 사용자를 식별하고 요청이 유효한지 확인한다.
- ~/.kube/config에 인증서가 있다!
- 그 다음, 요청은 인가(Authorization) 절차를 거친다. 요청이 유효한지 알았으니 사용자가 해당 작업을 수행할 수 있는 권한이 있는지 확인하는 작업이다.
- 이 절차는 역할 기반 접근 제어(RBAC)에 의해 수행될 수 있다.
- 권한이 있다면 파드를 생성, 제거, 업데이트, 나열할 수 있다.
- 아래는 역할이 가진 권한을 볼 수 있는 YAML 파일의 예시다.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: developer
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["update", "get", "create", "list", "delete"]
# 개발자에게 a 또는 b 라는 이름의 네임스페이스에서만 파드를 생성하도록 할 수 있다.
resourceNames: ["a", "b"]
이로써 RBAC로 생성할 수 있는 규칙은 kubernets api 수준에서 사용자에게 어떤 api 작업이 허용되는지 결정한다. 다르게 말해보자면 이것만 가능하다. 그러나 사용자가 이 이상의 작업을 하고자 한다면 어떻게 해야 할까?
예를 들어, 사용자가 파드 생성요청이 왔을 때, Public Registry에 있는 이미지를 사용하는 게 아닌 Private Registry에 있는 이미지를 사용하고 싶다고 할 수 있다. 혹은 모든 이미지에 "lataest" 태그를 사용하면 안된다는 규칙을 적용하고 싶어할 수 있다. 메타데이터에 반드시 레이블을 포함한다든가, 파드에게 특정 기능만을 허용한다든가 하는 필요가 있을 수 있다.
이렇기 때문에 Admission Controller가 필요하다.
Admission Controller
Admission Controller는 더 고도화된 보안 조치를 구현하는 데 필요한 컨트롤러다. 단순 구성요소를 변경하는 것만이 아니라 파드가 생성되기 전에 요청 자체를 변경하거나 추가 작업을 수행할 것을 제안하는 등 수많은 작업을 진행할 수 있다.
해당 컨트롤러에서 사용할 수 있는 플러그인들을 보자.
AlwaysPullImages
모든 Pod의 이미지 풀 정책을 Always로 강제하여, 노드에 이미지가 이미 있어도 항상 이미지를 다시 풀하도록 함. 멀티테넌트 환경에서 이미지 접근 제어에 유용함.
CertificateApproval
CertificateSigningRequest 리소스의 승인 요청을 감시하고, 승인 권한이 있는 사용자인지 추가로 확인함.
CertificateSigning
CertificateSigningRequest 리소스의 서명 요청을 감시하고, 서명 권한이 있는 사용자인지 추가로 확인함.
CertificateSubjectRestriction
특정 CertificateSigningRequest(예: kubernetes.io/kube-apiserver-client signer 사용)의 subject에 'system:masters' 그룹이 포함된 경우 요청을 거부함.
DefaultIngressClass
Ingress 객체 생성 시 ingressClass가 지정되지 않으면 기본 ingressClass를 자동으로 추가함. 기본 ingressClass가 여러 개면 오류 반환.
NamespaceAutoProvision
네임스페이스가 존재하지 않는 경우 자동으로 네임스페이스를 생성함.
NamespaceExists
네임스페이스가 존재하지 않으면 요청을 거부함. (Namespace 리소스 자체에는 적용되지 않음).
NamespaceLifecycle
삭제 중(Terminating)인 네임스페이스에는 새 오브젝트 생성 불가, 존재하지 않는 네임스페이스에 대한 요청 거부, 시스템 예약 네임스페이스 삭제 방지.
ValidatingAdmissionPolicy
CEL(Common Expression Language) 기반의 선언적 검증 정책을 적용하여, 조건에 맞지 않는 요청을 거부함.
ValidatingAdmissionWebhook
외부 HTTP 웹훅에 요청을 전달해 검증 정책을 동적으로 적용. 하나라도 거부하면 전체 요청이 거부됨.
플러그인을 하나 사용해보자. 먼저 어떤 플러그인이 활성화됐는지 확인해보자. admission은 직접 설정 파일을 보거나 명령어를 사용해서 확인할 수 있다.
# 1번
kube-apiserver -h | grep enable-admission-plugins
# 2번
cat /etc/kubernetes/manifests/kube-apiserver.yaml
2번을 실행했을 때 다음과 같은 활성화된 admission controller를 볼 수 있다. NodeRestriction는 kubelet이 수정할 수 있는 node와 pod를 제한한다. kubelet이 자격증명을 하지 않으면 타 노드의 자원을 수정할 수 없다!
이번엔 새로운 환경에서 파드를 생성한다고 가정해보겠다. 아래 명령어는 blue라는 네임스페이스에 nginx 파드를 생성하라는 명령어다.
kubectl run nginx --image=nginx --namespace=blue
이는 실행되지 않는다. 왜냐 하면 blue라는 네임스페이스는 현재 존재하지 않는다. 존재하지 않는 방 안에 가구를 배치할 수는 없는 노릇이다. 그러니 여기에 Admission controller를 추가하자.
apiVersion: v1
kind: Pod
metadata:
annotations:
kubeadm.kubernetes.io/kube-apiserver.advertise-address.endpoint: 192.168.96.166:6443
creationTimestamp: null
labels:
component: kube-apiserver
tier: control-plane
name: kube-apiserver
namespace: kube-system
spec:
containers:
- command:
- kube-apiserver
- --advertise-address=192.168.96.166
- --allow-privileged=true
- --authorization-mode=Node,RBAC
- --client-ca-file=/etc/kubernetes/pki/ca.crt
- --enable-admission-plugins=NodeRestriction,NamespaceAutoProvision
...
그러면 잠시 뒤 api server가 자동으로 재시작되고 해당 설정이 반영될 것이다. 그럼 그 상태에서 다시 위와 같은 명령어를 실행하면 존재하지 않던 네임스페이스가 생성되고 파드가 프로비저닝된다.
특정 컨트롤러를 비활성화하려면 disable키워드로 변경한 뒤 저장하면 된다.
- --disable-admission-plugins=DefaultStorageClass
그리고 다음 명령어로 설정에 명시한 컨트롤러를 확인해보면 된다.
ps -ef | grep kube-apiserver | grep admission-plugins
# 결과는 아래와 같다.
root 13877 13751 0 04:49 ? 00:00:03 kube-apiserver
--advertise-address=192.168.96.166
--allow-privileged=true
--authorization-mode=Node,RBAC
--client-ca-file=/etc/kubernetes/pki/ca.crt
--enable-admission-plugins=NodeRestriction,NamespaceAutoProvision
--disable-admission-plugins=DefaultStorageClass
--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
--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=172.20.0.0/16
--tls-cert-file=/etc/kubernetes/pki/apiserver.crt
--tls-private-key-file=/etc/kubernetes/pki/apiserver.key