Kubernetes 는 RBAC 이 디폴트 접근 제어 권한으로 User account 와 RBAC 을 적절히 사용하면 효과적인 접근 제어를 할 수 있습니다.

예를 들어, Kubernetes Cluster 에 접근할 수 있는 사용자 seungkyua 를 새로 만들고 해당 사용자는 seungkyua 네임스페이스에만 접근 가능하게 설정하고 싶은 경우가 있을 수 있습니다.

또한 seungkyua 네임스페이스 접근 안에서도 pods, replicasets, deployments, services 만 생성, 조회, 수정, 삭제권한만 주고 싶을 때도 있습니다.

 

이러한 세팅은 아래와 같은 절차를 거쳐야 합니다. 물론 Kubernetes 는 RBAC 사용으로 세팅되어 있다고 가정합니다.

  1. 신규 namespace 생성
  2. User account 생성 (client key 와 client crt 생성)
  3. 새로운 namespace 에 제한적으로 접근할 수 있는 권한 생성 (role 생성)
  4. 3번의 새로운 User account 와 3 번의 role 을 연결 (rolebinding 생성)
  5. kubectl 이 사용할 새로운 config context 를 만들고 연결

 

1. 신규 naemspace 생성

# kubectl create namespace seungkyua

 

2. 새로운 User account 생성

  • client private key 생성 
# openssl genrsa -out seungkyua.key 2048

 

  • crt 생성을 위한 csr 생성
# openssl req -new -key seungkyua.key -subj "/CN=seungkyua/O=Kubernetes Korea Group" -out seungkyua.csr

 

  • client cert 파일 생성
# openssl x509 -req -in seungkyua.csr -CA /etc/kubernetes/ssl/ca.crt -CAkey /etc/kubernetes/ssl/ca.key -CAcreateserial -out seungkyua.crt -days 10000

 

3. 새로운 namespace 로 접근 role 설정

# vi role.yaml

apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:
  name: seungkyua-manager
  namespace: seungkyua
rules:
  - apiGroups:
      - ""            # core api
      - "extensions"
      - "apps"
    resources:
      - "deployments"
      - "replicasets"
      - "pods"
      - "services"
    verbs:
      - "get"
      - "list"
      - "watch"
      - "create"
      - "update"
      - "patch"
      - "delete"


# kubectl create -f role.yaml

 

4. User 와 Role 을 연결 (rolebinding 생성)

# vi rolebinding.yaml

apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
  name: seungkyua-manager-rolebinding
  namespace: seungkyua
subjects:
  - kind: User         # User or ServiceAccount
    name: seungkyua
roleRef:
  kind: Role
  name: seungkyua-manager
  apiGroup: rbac.authorization.k8s.io
  

# kubectl create -f rolebinding.yaml

 

5. Kubectl config context 생성

  • 인증을 위한 사용자 정보 등록 (key, crt 로 접속)
# kubectl config set-credentials seungkyua --client-certificate=/root/ahnsk/private-certs/seungkyua.crt --client-key=/root/ahnsk/private-certs/seungkyua.key --embed-certs=true

 

  • context 생성
# kubectl config get-clusters NAME cluster.local # kubectl config set-context seungkyua@k1 --cluster=cluster.local --user=seungkyua --namespace=seungkyua

 

  • kubernetes 접속
# kubectl config get-contexts
CURRENT   NAME                             CLUSTER         AUTHINFO           NAMESPACE
*         kubernetes-admin@cluster.local   cluster.local   kubernetes-admin   
          seungkyua@k1                     cluster.local   seungkyua 


# kubectl config use-context seungkyua@k1


# kubectl config get-contexts
CURRENT   NAME                             CLUSTER         AUTHINFO           NAMESPACE
          kubernetes-admin@cluster.local   cluster.local   kubernetes-admin   
*         seungkyua@k1                     cluster.local   seungkyua          seungkyua

 

Kubernetes 에 접속하여 해당 계정이 해당 네임스페이스에만 접속 권한이 있는지 확인

# kubectl get pods

No resources found.



# kubectl get pods -n defaults

Error from server (Forbidden): pods is forbidden: User "seungkyua" cannot list resource "pods" in API group "" in the namespace "defaults"

 

 

Posted by Kubernetes Korea co-leader seungkyua@gmail.com

Kubernetes Node 가 NotReady 인 경우 해결을 위해서 제일 먼저 해당 노드의 syslog 를 확인 하는 것이 좋다.


## Node 의 syslog 확인 (ubuntu 인 경우)
# vi /etc/log/syslog

Feb 20 12:14:49 k1-node01 kubelet[31100]: E0220 12:14:49.401890   31100 raw.go:146] Failed to watch directory "/sys/fs/cgroup/devices/system.slice": inotify_add_watch /sys/fs/cgroup/devices/system.slice/run-ref5f878051f440b98dfd9bd843a01b58.scope: no space left on device
Feb 20 12:14:49 k1-node01 kubelet[31100]: F0220 12:14:49.401909   31100 kubelet.go:1369] Failed to start cAdvisor inotify_add_watch /sys/fs/cgroup/devices/system.slice/run-ref5f878051f440b98dfd9bd843a01b58.scope: no space left on device
Feb 20 12:14:49 k1-node01 kubelet[31100]: E0220 12:14:49.640153   31100 kubelet.go:2266] node "k1-node01" not found
Feb 20 12:14:49 k1-node01 systemd[1]: kubelet.service: Main process exited, code=exited, status=255/n/a
Feb 20 12:14:49 k1-node01 systemd[1]: kubelet.service: Unit entered failed state.
Feb 20 12:14:49 k1-node01 systemd[1]: kubelet.service: Failed with result 'exit-code'.


위와 같이 inotify_add_watch 에서 no space left on device 로 에러가 날 경우에 sysctl 로 값을 늘려줘야 한다.

## fs.inotify.max_user_watches 값을 늘려줌
# vi /etc/sysctl.d/99-sysctl.conf

net.ipv4.ip_forward=1
net.ipv4.ip_local_reserved_ports=30000-32767
net.bridge.bridge-nf-call-iptables=1
net.bridge.bridge-nf-call-arptables=1
net.bridge.bridge-nf-call-ip6tables=1
fs.inotify.max_user_watches=1048576


해당 값을 적용하고 나서 kubelet 이 activating 상태이거나 failed 상태인 경우가 많으므로 아래와 같이 kubelet 을 재 시작해 준다.

## sysctl 로 적용 및 kubelet 재실행

# sysctl -p
# sudo systemctl restart kubelet 






Posted by Kubernetes Korea co-leader seungkyua@gmail.com

kubernetes 에서 ceph rbd provisioner 활용 방법입니다.


1. 먼저 rbd provisioner 가 사용할 rbac 권한 yaml 파일을 만듭니다.
# vi rbd-rbac.yaml
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: rbd-provisioner
rules:
  - apiGroups: [""]
    resources: ["persistentvolumes"]
    verbs: ["get", "list", "watch", "create", "delete"]
  - apiGroups: [""]
    resources: ["persistentvolumeclaims"]
    verbs: ["get", "list", "watch", "update"]
  - apiGroups: ["storage.k8s.io"]
    resources: ["storageclasses"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["events"]
    verbs: ["create", "update", "patch"]
  - apiGroups: [""]
    resources: ["services"]
    resourceNames: ["kube-dns","coredns"]
    verbs: ["list", "get"]
  - apiGroups: [""]
    resources: ["endpoints"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]

---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: rbd-provisioner
subjects:
  - kind: ServiceAccount
    name: rbd-provisioner
    namespace: kube-system
roleRef:
  kind: ClusterRole
  name: rbd-provisioner
  apiGroup: rbac.authorization.k8s.io

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: rbd-provisioner
  namespace: kube-system

---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: rbd-provisioner
  namespace: kube-system
rules:
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["get"]
- apiGroups: [""]
  resources: ["endpoints"]
  verbs: ["get", "list", "watch", "create", "update", "patch"]

---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: rbd-provisioner
  namespace: kube-system
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: rbd-provisioner
subjects:
- kind: ServiceAccount
  name: rbd-provisioner
  namespace: kube-system 

# kubectl create -f rbd-rbac.yaml 



2. kube-system 네임스페이스로 admin secret 과 user secret 을 생성한다.
# ceph auth get client.admin 2>&1 |grep "key = " |awk '{print  $3'} |xargs echo -n > /tmp/secret.admin
# kubectl create secret generic ceph-secret-admin --type=kubernetes.io/rbd --from-file=/tmp/secret.admin --namespace=kube-system

# ceph auth get-key client.kube > /tmp/secret.user
# kubectl create secret generic ceph-secret-user --type=kubernetes.io/rbd --from-file=/tmp/secret.user --namespace=kube-system 




3. rbd provisioner 가 사용할 storage class yaml 파일을 만듭니다.
pool 이름과 userId 를  정확히 입력해야 합니다.

# vi rbd-storageclass.yaml

---
apiVersion: storage.k8s.io/v1beta1
kind: StorageClass
metadata:
  name: "rbd"
  annotations:
    storageclass.beta.kubernetes.io/is-default-class: "true"
provisioner: ceph.com/rbd
reclaimPolicy: Delete
parameters:
  monitors: "192.168.30.23:6789,192.168.30.24:6789,192.168.30.25:6789"
  adminId: "admin"
  adminSecretName: "ceph-secret-admin"
  adminSecretNamespace: "kube-system"
  pool: "kubes"
  userId: "kube"
  userSecretName: "ceph-secret-user"
  userSecretNamespace: "kube-system"
  imageFormat: "2"
  imageFeatures: "layering" 


# kubectl create -f rbd-storageclass.yaml 



4. rbd provisioner 를 deployment 타입 yaml 로 생성합니다.
# vi deployment-rbd-provisioner.yaml

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: rbd-provisioner
  namespace: kube-system
  labels:
    app: rbd-provisioner
    version: v2.1.1-k8s1.11
spec:
  replicas: 2
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: rbd-provisioner
      version: v2.1.1-k8s1.11
  template:
    metadata:
      labels:
        app: rbd-provisioner
        version: v2.1.1-k8s1.11
    spec:
      priorityClassName: system-cluster-critical
      serviceAccount: rbd-provisioner
      containers:
        - name: rbd-provisioner
          image: quay.io/external_storage/rbd-provisioner:v2.1.1-k8s1.11
          imagePullPolicy: IfNotPresent
          env:
            - name: PROVISIONER_NAME
              value: ceph.com/rbd
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
          command:
            - "/usr/local/bin/rbd-provisioner"
          args:
            - "-id=${POD_NAME}" 


# kubectl create -f deployment-rbd-provisioner.yaml 




5. 이제 default 네임스페이스에 테스트를 해 보겠습니다.
먼저 default 네임스페이스에 user secret 을 생성합니다. (rbd provisioner 를 사용하고자 하는 네임스페이스에는 user secret 을 만들어 줘야 합니다.)
# ceph auth get-key client.kube > /tmp/secret.user
# kubectl create secret generic ceph-secret-user --type=kubernetes.io/rbd --from-file=/tmp/secret.user --namespace=default


6. 테스트용 pod 생성.

# vi rbd-test.yaml

---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: rbd-test
  namespace: default
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: rbd
  resources:
    requests:
      storage: 1Gi

---
apiVersion: v1
kind: Pod
metadata:
  name: rbd-test
  namespace: default
spec:
  containers:
  - name: pod-test
    image: gcr.io/google_containers/busybox:1.24
    command:
    - "/bin/sh"
    args:
    - "-c"
    - "touch /mnt/SUCCESS && exit 0 || exit 1"
    volumeMounts:
    - name: pvc
      mountPath: "/mnt"
  restartPolicy: "Never"
  volumes:
  - name: pvc
    persistentVolumeClaim:
      claimName: rbd-test 



# kubectl create -f rbd-test.yaml 





Posted by Kubernetes Korea co-leader seungkyua@gmail.com

일반적으로 RBAC 으로 Kubernetes Dashboard 를 설치하면 service account 의 token 을 가지고 접속해야 합니다.


admin clusterrole 을 가진 secret 을 찾아서 해당 token 으로 접속합니다.

$ kubectl -n kube-system describe secret clusterrole-aggregation-controller-token-fmnfg


Name:         clusterrole-aggregation-controller-token-fmnfg

Namespace:    kube-system

Labels:       <none>

Annotations:  kubernetes.io/service-account.name=clusterrole-aggregation-controller

              kubernetes.io/service-account.uid=ccbaf065-8f01-11e8-80c8-3ca82a1ccfd4


Type:  kubernetes.io/service-account-token


Data

====

ca.crt:     1025 bytes

namespace:  11 bytes

token:      xxxxxxxxxxxxxxxxxxxxxxxxxxxx


매번 이런 token 방식이 귀찮으면 아래와 같이 dashboard service account 에 admin clusterrole 을 지정하고 로그인 화면에서는 skip 을 클릭하여 넘어가면 됩니다.

$ kubectl get serviceaccount -n kube-system | grep kubernetes-dashboard

kubernetes-dashboard                 1         8d 


$ kubectl create clusterrolebinding kubernetes-dashboard-admin \

--clusterrole=cluster-admin \

--serviceaccount=kube-system:kubernetes-dashboard





Posted by Kubernetes Korea co-leader seungkyua@gmail.com


Kubernetes dashboard 를 Ingress Controller 와 연결할 때 SSL 을 적용하고 싶으면 다음과 같이 Ingress 를 생성하면 된다.



Kubernetes dashboard ingress 


apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/secure-backends: "true"
ingress.kubernetes.io/ssl-passthrough: "true"
nginx.org/ssl-backend: "kubernetes-dashboard"
kubernetes.io/ingress.allow-http: "false"
nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/proxy-body-size: "100M"
name: kubernetes-dashboard
namespace: kube-system
spec:
tls:
- secretName: kubernetes-dashboard-certs
rules:
- host: dashboard.k8s.stage
http:
paths:
- path: /
backend:
serviceName: kubernetes-dashboard
servicePort: 443



Posted by Kubernetes Korea co-leader seungkyua@gmail.com

Kubernetes pause infra container 는 기존의 golang 에서 c 로 변경되었다.


main 함수를 보면 다음과 같다.

int main(int argc, char **argv) {
int i;
for (i = 1; i < argc; ++i) {
if (!strcasecmp(argv[i], "-v")) {
printf("pause.c %s\n", VERSION_STRING(VERSION));
return 0;
}
}

if (getpid() != 1)
/* Not an error because pause sees use outside of infra containers. */
fprintf(stderr, "Warning: pause should be the first process\n");

if (sigaction(SIGINT, &(struct sigaction){.sa_handler = sigdown}, NULL) < 0)
return 1;
if (sigaction(SIGTERM, &(struct sigaction){.sa_handler = sigdown}, NULL) < 0)
return 2;
if (sigaction(SIGCHLD, &(struct sigaction){.sa_handler = sigreap,
.sa_flags = SA_NOCLDSTOP},
NULL) < 0)
return 3;

for (;;)
pause();
fprintf(stderr, "Error: infinite loop terminated\n");
return 42;
}


pause 는 infra container 이므로 getpid() 가 1 인 최상의 프로세스가 되는 것이 Security 측면에서 바람직하다.


sigaction 은 signal 이 SIGINT (키보드 ctrl + c 로 종료), SIGTERM (종료) 가 들어오면 sigdown 함수를 handler 로 등록해서 수행한다.

자식 프로세스가 종료되거나 정지/재시작 될 때 SIGCHLD signal 이 발생하는데, SA_NOCLDSTOP flag 는 자식 프로세스가 정지되는 4가지의 signal - SIGSTOP(프로세스 정지), SIGTSTP(키보드 ctrl + z 로 발생한 프로세스 정지), SIGTTIN(백그라운드에서 제어터미널 읽기를 시도해서 정지), SIGTTOU(백그라운드에서 제어터미널 쓰기를 시도해서 정지) 등을 받아서 정지되거나 CONTINUE signal 을 받아서 재시작되어도 이를 통지 받지 않겠다는 의미이다. 즉, pause 가 부모 프로세스가 이지만 SIGCHLD signal 을 통보받을 필요가 없다고 생각하는 것이다.  하지만 그 나머지인 경우인 자식 프로세스가 종료되는 경우에는 signal 을 받을 수 밖에 없다. 이 때는 waitpid 함수를 통해서 혹시라도 자식 프로세스가 좀비가 되었을 때 좀비 프로세스를 제거할 수 있다.


sigaction 함수는 에러가 발생하면 -1 을 정상 처리되면 0을 리턴한다.


static void sigdown(int signo) {
psignal(signo, "Shutting down, got signal");
exit(0);
}

psignal 함수는 두번째 인자로 들어온 string 을 stderr 로 출력한다.



static void sigreap(int signo) {
while (waitpid(-1, NULL, WNOHANG) > 0)
;
}

자식 프로세스가 왜 종료 되었는지는 관심이 없고 단순히 자식 프로세스가 종료될 때 좀비 프로세스를 막고자 한다면 waitpid 함수를 위와 같이 사용한다.


첫번재 인자로 전달된 pid 값이 -1 이면 모든 자식 프로세스가 종료될 때 까지 기다린다. 하지만 마지막 인자로 WNOHANG 이 입력되면 부모 프로세스는 기다리지 않고 바로 리턴된다. 하나의 SIGCHLD 에 대해서 while 문으로 처리한 이유는 여러 자식 프로세스가 종료될 수 있는 가능성이 있기 때문이다. 즉, SIGCHLD signal 이전에 좀비 프로세스가 있으면 그것을 처리한다. 


그럼 결과적으로는 SIGCHLD signal 이 발생할 때 pause 프로세스는 아무것도 기다리지 않고 좀비 프로세스가 있으면 처리하고 바로 리턴한다.

그리고 마지막에 pause() 함수에 의해서 잠시 정지 상태가 된다. 



* 좀비 프로세스 : 자식 프로세스가 종료되어 사용하는 리소스는 모두 해제된 상태지만, 부모 프로세스가 자식 프로세스의 종료를 확인하지 못한 상태로 커널의 프로세스 테이블에는 관리되고 있는 상태

* 고아 프로세스 : 자식 프로세스 보다 부모 프로세스가 죽었을 경우 자식 프로세스가 pid 1 인 init 프로세스에 속하게 된 경우




 



 
















Posted by Kubernetes Korea co-leader seungkyua@gmail.com


2018년 3월 26일 CNC & Kubernetes Meetup 발표 자료


 facebook group : https://www.facebook.com/groups/k8skr/


• 일시: 2018년 3월 26일(월) 오후 7시 30분 - 9시 30분

• 장소: 역삼역 7번 출구 GS타워 12층 AWS코리아 (강남구 논현로 508)

• 세션 내용:


19:30-20:10 Kubernetes 를 이용한 Cloud native platform 개발 (어형부형)



20:10-20:50 Helm chart 를 활용한 App 배포 방법 (안승규)








Posted by Kubernetes Korea co-leader seungkyua@gmail.com

OpenStack 발표자료 (From Kubernetes to OpenStack)





Posted by Kubernetes Korea co-leader seungkyua@gmail.com


[ dynamic volume 사용법 ]
1. pvc 에 storageclass 를 지정하여 pvc 만 생성하면 pv 가 다이너믹하게 생성되고 pvc 도 생성된다.
2. 이 때 rbd 이미지도 자동으로 생성된다.

$ vi jenkins-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: jenkins
  namespace: ci-infra
spec:
  accessModes:
  - ReadWriteMany
  resources:
    requests:
      storage: 100Gi
  storageClassName: ceph




[ static volume 사용법 ]
1. rbd 이미지를 수동으로 생성해야 한다.
2. pv 에 storageclass 와 rbd 값을 모두 넣어야 한다.
    pv 에 pvc 에서 pv 를 selector 로 찾을 수 있게 label 값을 넣어야 한다.
    (keyring 값은 안넣어도 됨, storageclass의 secret 이용)
3. pvc 에 storageclass 와 selector 나 volumeName 둘 중에 하나를 사용하여 pv 와 연결한다.
    (storageclass 는 값은 없어도 됨.  없으면 default 인 storageclass 값을 활용함)


$ vi jenkins-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: jenkins
  labels:
    app: jenkins
spec:
  capacity:
    storage: 100Gi
  accessModes:
  - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  storageClassName: ceph
  rbd:
    image: jenkins
    monitors:
    - 192.168.30.23:6789
    - 192.168.30.24:6789
    - 192.168.30.25:6789
    pool: kubes
    secretRef:
      name: ceph-secret-user
    user: kube


$ vi jenkins-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: jenkins
  namespace: ci-infra
spec:
  accessModes:
  - ReadWriteMany
  resources:
    requests:
      storage: 100Gi
  storageClassName: ceph
  selector:
    matchLabels:
      app: jenkins
# volumeName: jenkins


$ vi jenkins-deployment.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: jenkins
  namespace: ci-infra
  labels:
    app: jenkins
spec:
  replicas: 1
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: jenkins
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: cicd-services
                operator: In
                values:
                - enabled
      securityContext:
        runAsUser: 1000
        fsGroup: 1000
      containers:
      - name: master
        env:
        - name: JENKINS_OPTS
          value: "--httpsPort=0 --http2Port=0"
        - name: JAVA_OPTS
          value: "-Xms8G -Xmx8G -XX:+UseG1GC -XX:+ExplicitGCInvokesConcurrent -XX:+ParallelRefProcEnabled -XX:+UseStringDeduplication -XX:+UnlockExperimentalVMOptions -XX:G1NewSizePercent=20 -XX:+UnlockDiagnosticVMOptions -XX:G1SummarizeRSetStatsPeriod=1 -Dorg.apache.commons.jelly.tags.fmt.timeZone=Asia/Seoul"
        image: jenkins/jenkins:latest
        imagePullPolicy: Always
        ports:
        - containerPort: 8080
          name: http
          protocol: TCP
        - containerPort: 50000
          name: jnlp
          protocol: TCP
        readinessProbe:
          httpGet:
            path: /login
            port: 8080
          periodSeconds: 10
          timeoutSeconds: 5
          successThreshold: 2
          failureThreshold: 5
        volumeMounts:
        - mountPath: /var/jenkins_home
          name: jenkins
#        resources:
#          limits:
#            cpu: 4000m
#            memory: 8000Mi
#          requests:
#            cpu: 1000m
#            memory: 8000Mi
      volumes:
      - name: jenkins
        persistentVolumeClaim:
          claimName: jenkins




## 생성
$ rbd create kubes/jenkins -s 100G
$ kubectl create -f jenkins-pv.yaml
$ kubectl create -f jenkins-pvc.yaml

$ kubectl create -f jenkins-deployment.yaml 


Posted by Kubernetes Korea co-leader seungkyua@gmail.com






Posted by Kubernetes Korea co-leader seungkyua@gmail.com