포스트

Cka 정리5

cka 정리5

165: Service Accounts

k8s의 서비스 계정에 대하여 다룬다. 서비스 계정은 authentication, authorization, role-based access controll를 비롯한 k8s의 다른 보안과 연결된다. 다소 CKAD 과정에 속하지만 얕게 소개하겠다.

k8s에는 사용자 계정과 서비스 계정 두 유형의 계정이 있다.

  • 사용자 계정
    • 관리 작업 수행
    • 클러스터 접근하는 관리자 or 애플리케이션 배포하는 개발자
  • 서비스 계정
    • k8s 클러스터와 상호작용하기 위해 애플리케이션에서 사용하는 계정
    • ex1) Prometheus 같은 모니터링 애플리케이션은 서비스 계정 이용해 k8s API에 접근하여 성능 메트릭 측정해 가져옴
    • ex2) jenkins같은 자동화된 빌드 tool은 서비스 계정을 이용해 k8s 클러스터에 애플리케이션을 배포함
    • 애플리케이션(서비스)가 k8s API에 쿼리하려면 인증을 받아야함. 이 때 서비스 계정을 사용한다.

      명령어

      생성

  • $ kubectl create serviceaccount account-name 조회
  • $ kubectl get serviceaccount
  • pod이 접근할 수 있는 위치에 서비스 계정 토큰이 있어야 하는데 이는 다음 경로에서 확인 가능하다
    • Containsers-Mounts 을 보면 /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access 가 있다.

서비스 계정이 생성되면 토큰도 자동으로 생성된다. 이 토큰은 외부에서 k8s API에 인증할때 쓰는 것이며 secret 오브젝트로 저장된다. k describe secret secret-name 으로 볼 수 있다 그리고 이 토큰을 이용해 curl 명령어를 통한 api 호출의 인증 토큰으로 사용할 수 있다

물론 서비스 계정을 생성하고 RBAC(role-based-access-control)을 사용할 수도 있지만 이는 범위를 벗어난다. 서비스 계정을 생성할 때 만들어진 토큰을 이용해 외부 앱이 k8s에 인증하도록 하는 방법을 사용한다

그런데 만약 타사 애플리케이션이 외부가 아니라 클러스터 안에 같이 있다면 어떨까? 이 경우 서비스 계정 토큰을 내보내고 이를 사용하도록 타사 애플리케이션을 구성하는 과정을 단순하게 만들 수 있다. 서비스 토큰 암호를 타사 애플리케이션을 호스팅하는 파드 내부의 볼륨으로 자동 마운트하면 된다. 그럼 k8s에 엑세스하기 위한 토큰이 이미 파드 내부에 배치되어 앱이 쉽게 읽을 수 있어 수동으로 제공라지 않아도 된다.

클러스터에 외부 앱인 k8s 대시보드 파드를 띄우는 경우

  • 기본 default 서비스 계정이 존재하는데 k8s의 모든 namespace에 대해 default 라는 계정이 존재한다.
  • 파드가 생성될 때마다 default 서비스 계정과 해당 토큰이 볼륨 마운트로 해당 파드에 자동으로 마운트된다

  • pod definition yaml에 서비스 계정 토큰을 담은 secret이나 volume mount 등의 작업을 하지 않았다.
  • 그러나 파드가 생성되고 파드 세부 정보를 보면 default 토큰이라는 시크릿에서 자동으로 볼륨이 생성되는 것을 볼 수 있습니다. -> 볼륨을 마운트해줄 필요없이 해당 서비스계정의 토큰이 존재하는 볼륨에서 애초에 파드가 시작된다.
  • 토큰은 var/run/secrets/kubernetes.io/serviceaccount 에 있는데 ls하면 ca.crt namespace 그리고 token이 있다.

+ default 계정은 매우 제한적인데 다른 서비스 계정을 이용하려면 서비스 계정을 포함하도록 파드 definition 파일을 수정하고 새 서비스 계정의 이름을 지정한다. 기존 파드의 서비스 계정은 편집할 수 없어서 파드를 삭제하고 다시 생성해야 합니다. 그러나 deployment의 경우 파드 definition 파일을 변경하면 배포에 대한 새 롤아웃이 자동으로 트리거되므로 서비스 계정을 사용할 수 있다. deployment는 새로운 서비스 계정으로 새 파드를 삭제하고 다시 생성하는 작업을 처리해준다

  • pod yaml 파일에 spec 아래에 serviceAccountName에 서비스 계정명을 넣어주면 된다

    버젼 1.24 변경사항

    서비스계정 생성시 토큰이 secret형태로 자동 생성되지 않는다.

별도로 생성한 뒤 secret def yaml 파일에 사비스 계정과 함께 명시해서 연결해주어야 한다

웹 대시보드에서 token을 입력해야하는 경우 k create token dashboard-sa로 생성한 토큰을 넣어주면 된다.

167: Image Security

Docker Image의 보안에 대하여 알아보자 Secure 이미지 레포지토리와 그 이미지를 사용하도록 pods를 구성하는 방법이 있다. 간단한 하나의 파드 예시를 보자 spec: containers:

  • name: nginx image: nginx

nginx 이 간단한 이름은 어떤 약어일까? 앞에 무엇이 생략돼있는걸까?

  • docker.io/nginx/nginx 이다
  • {registry}/{user/account}/{image/repository} 의미이다

registry의 default는 docker hub이고 AWS, Azure, GCP 등 많은 클라우드 서비스가 private registry를 제공한다.

Private Registry

Docker hub, google registry 혹은 개인 registry에서 레포를 비공개로 만들수도 있다.

Docker 관점에서 Private 이미지를 사용해 컨테이너를 실행하려면 먼저 Docker login으로 프라이빗 레포에 로그인한다

  • $ docker login private-registry.io 자격 증명(credential) 입력 파드 definition에서 이미지 이름을 개인 registry 전체 경로로 변경함

그러나 매번 로그인하고 credential을 입력하는 과정을 k8s 클러스터 파이프라인에 넣을 순 없는 노릇이다. 로그인 인증 부분을 k8s secret 오브젝트로 해결한다.

docker-registry를 위한 secret은 docker-registry type이어야 하며 다음 정보들이 필요하다

  • name
  • username
  • passwd
  • server
  • email
  • secret-type: docker-registry

imperative create

  • $ kubectl create secret docker-registry private-reg-cred –docker-username=docker_user –docker-password=docker_password –docker-email=dock_user@myprivateregistry.com –docker-server=myprivateregistry.com:5000

이후 생성한 secret을 pod의 spec에 넣어주면 된다 apiVersion: v1 kind: Pod metadata: name: private-reg spec: containers: - name: private-reg-container image: your-private-image imagePullSecrets: - name: regcred

170: Pre-requisite - Security in Docker

사전지식 - 도커의 보안 Docker가 설치된 호스트에는 여러 OS 프로세스, Docker 데몬, SSH 서버 등 같이 실행중인 자체 프로세스 세트가 있다. 한시간동안 대기하는 프로세스를 실행하는 Ubuntu Docker 컨테이너를 실행한다고 가정해보자 (docker run ubuntu sleep 3600)

  • 컨테이너는 호스트에서 완전히 격리되지 않는다.
  • 둘은 같은 커널을 공유한다
  • 컨테이너는 Linux에서 namespace를 이용해 격리된다.
  • 호스트에는 namespace가 있고 컨테이너는 자체 namespace가 있다.
  • 컨테이너에 의해 실행되는 모든 프로세스는 자체 namespace에서 실행된다고 볼 수도 있다

Docker 컨테이너는 자체 namespace에 있으며 자체 프로세스만 볼 수 있다. 외부 또는 다른 namespace에서 아무것도 볼 수 없다. Docker 호스트의 경우 자체 프로세스와 자식 namespace 프로세스들을 각기 다른 프로세스들로 표현한다. 이는 프로세스가 서로 다른 namespace에서 서로 다른 pid를 가질 수 있고 Docker가 시스템 내에서 컨테이너를 이런 방식으로 격리하기 때문이다.

Container Security

보안 맥락에서 유저를 살펴보자. Docker 호스트에는 사용자 집합(root, non-root users)이 있고 여러 사용자들이 있다. default로 Docker는 컨테이너 내에서 root user로 프로세스를 실행한다.

  • 프로세스를 root로 실행하지 않으려면 docker run 커맨드에 유저 옵션을 주면 된다.
    • $ docker run –user=1000 ubuntu sleep 3600

혹은 docker image 자체에 user를 명시해 정의한다

  • “USER 1000”

그렇다면 컨테이너를 루트 사용자로 실행한다면 어떨까 컨테이너 내의 루트와 호스트의 루트가 같을까? 컨테이너 내부의 root는 외부의 root가 시스템에서 수행할 수 있는 모든 작업을 수행할 수 있나? 그럼 위험하지 않나? -> Docker는 컨테이너 내에서 루트 사용자의 능력을 제한하는 보안이 있다 => 즉, 컨테이너 내의 root는 호스트의 root와 다르다.

루트는 다양한 권한을 가지며 capability. /user/include/linux/capability.h 에서 전체 목록을 볼 수 있다.

default로는 제한된 capability 세트로 컨테이너를 실행한다. 동작을 재정의하고 추가 권한을 제공하기

  • $ docker run –cap-add MAC_ADMIN ubuntu 권한을 삭제하기
  • $ docker run –cap-drop KILL ubuntu 모든 권한 활성화된 상태로 컨테이너 실행하기
  • $ docker run –privileged ubuntu

    171: Security Contexts

    docker 컨테이너를 실행할 때 유저 id, capabilty 등을 추가할 수 있는 옵션이 있었고 이는 k8s에서도 구성할 수 있다. k8s에서 컨테이너는 파드에 캡슐화되므로 컨테이너 level 혹은 파드 level에서 보안 설정을 구성할 수 있다.

  • pod level로 구성하면 설정이 pod 내 모든 컨테이너들을 일괄설정하게 된다.
  • pod와 container 모두 구성하면 컨테이너 설정이 pod 설정을 override한다.

    pod level

    apiVersion: v1 kind: Pod metadata: name: private-reg spec: securityContext: runAsUser: 1000 containers:

  • name: private-reg-container image: your-private-image

    container level

    spec: containers:

  • name: private-reg-container image: your-private-image securityContext: runAsUser: 1000

+capability 추가하기 spec: containers: - name: private-reg-container image: your-private-image securityContext: runAsUser: 1000 capabilities: add: (mac admin)

user를 바꾸는 것(runAsUser)은 pod를 edit하면 적용되지 않는다. 지우고 새로 만들어야함

174: Network Policy

웹 서버, API 서버, DB 서버 관점에서 인바운드와 아웃바운드 (ingress / egress)가 있다

  • 보안을 위해서는 각 주체 입장에서 들어오는 ingress 트래픽과 egress 트래픽에 대해서 각각 네트워트 트래픽 설정을 해주면 될 것이다

    Network Security

    k8s의 네트워크 보안을 살펴보자. 클러스터는 pods, services를 호스팅하는 노드들이 있다. 각 노드에는 IP가 있으며 각 파드와 서비스도 마찬가지이다. k8s 네트워킹의 전제 조건 중 하나는 파드들끼리는 route 추가 설정 없이 서로 통신할 수 있어야한다는 것이다.

k8s는 default로 모든 파드에서 클러스터 내의 다른 파드 또는 서비스로 트래픽을 모두 허용한다.

Network Policy

프론트 웹 서버가 DB 서버랑 직접 통신할 수 없게 하려면 어떻게 해야하는가? 중간 다리인 API 서버에 DB서버와 웹 서버로의 트래픽에 대한 네트워크 정책을 구현해 연결하면 된다. 네트워크 정책은 pod, replicaSet, Service와 같은 k8s namespace의 또 다른 오브젝트이며 하나 이상의 파드에 연결할 수 있다.

DB에서 API서버로부터의 3306 포트 요청을 승인하는 정책은 다음처럼 만들 수 있다.

  • db pod는 물론 label에 “role: db” 를 준비해놓아야 해당 정책이 연결될 것이다
  • policytypes에 ingress 하나만 있으므로 egress는 어떠한 제한도 받지 않음을 의미한다 정책을 만들었다면 실행하면 된다.
  • $ kubectl create -f policy-definition.yaml 정책 확인
  • $ kubectl get networkpolicies

    175: Developing network policies

    k8s는 default로 모든 파드에서 모든 대상으로의 모든 트래픽을 허용한다.

    1. ingress / egress를 모두 차단한다
  1. db서버에 들어오는 것을 제한한다.

그런데 만약 클러스터 안에서 같은 레이블이지만 namespace가 다른 여러 API 파드가 있다면 어떡할까?

NetworkPolicy에 namespace를 설정해주면 된다

그러나 만약 부득이하게 다른 namespace인 staging에 있는 파드가 prod namespace에 있는 파드에 접근하고자 하면 어떻게 해야할까?

  • namespaceSelector를 사용하면 된다.

    외부 서버의 트래픽을 설정하는 경우

  • 외부 서버라면 k8s 클러스터 내에 함께 있는 것이 아니므로 트래픽을 라우팅하는 데 사용하는 podSelector와 namespaceSelector 필드는 작동하지 않는다.
  • 특정 IP에서의 트래픽을 제한하므로 ipBlock 필드를 사용한다

  • podSelector: label로 pod를 선택한다
  • namespaceSelector: namespace를 선택한다
  • ipBlock: IP 주소 범위를 선택한다

다만 from 하위에 위 이미지처럼 두 묶음이 있다. 이럼 or 조건이다. 대시가 2개 이므로 첫 대시 에서 api-pod label도 맞야 하고 namespace 또한 prod여서 첫 조건을 만족하거나 ipBlock 에 속하거나 인 것이다.

여러개인 경우는

  • from 혹은 - to 를 여러개 쓰면 된다. 그럼 한번에 여러 규칙을 여러 포트로 내보낼 수 있다.

179: Storage - Section Introduction

  • persistent volume
  • persistent volume claim
  • access mode 등을 다룬다.

    180: Introduction to Docker Storage

    k8s와 같은 컨테이너 orchestration 도구의 storage를 이해하려면 storage가 container와 작동하는 방식을 알아야 한다. docker와 storage 관계를 먼저 이해하면 k8s와 storage의 관계도 이해할 수 있다.

    181: Storage in Docker

    Docker를 설치하면 /var/lib/docker/에 다음 구조가 생긴다

  • docker가 default로 모든 데이터를 저장하는 곳이다

    Layer Architecture

  • docker image의 라인마다 레이어를 만든다
  • 빌드하면 해당 과정이 일어나고 이 레이어들은 read only이다. 수정하려면 새로 빌드해야한다

  • docker run으로 실행하면 image layer 기반으로 컨테이너를 만들고 Writable한 새 layer를 만든다
  • 이 layer에서는 애플리케이션이 작성한 로그, 컨테이너의 임시 파일, 등 컨테이너가 생성한 데이터를 저장하는 데 사용된다.
  • 하지만 이 레이어의 수명은 컨테이너가 살아있는 동안만 지속된다. (소멸시 함꼐 소멸)

    Volumes

    컨테이너가 제거되면 컨테이너 레이어의 데이터도 삭제된다. 이런 데이터들을 유지하려면 어떻게 해야할까? -> Persistent Volume을 추가하고 붙이면 된다.

  • $ docker run -v data_volume:/var/lib/mysql mysql

182: Volume Driver Plugins in Docker

  • aws ebs 볼륨을 붙여 데이터를 클라우드에 안전하게 보관할 수 있다.

183: Container Storage Interface (CSI)

Container Runtime Interface(CRI)

과거 k8s는 컨테이너 런타임으로 Docker만 사용했고 도커와 연동되는 모든 코드는 k8s 소스코드에 있었다. 그러나 Rockey, CRI-O와 같은 다른 컨테이너 런타임가 등장함에 따라 k8s는 다른 컨테이너 런타임으로 작업할 수 있도록 지원하고 확장하는 게 중요했다. 그래서 CRI가 탄생했다.

Container Storage Inferface

k8s 같은 orchestration 솔루션이 여러 스토리지 솔류션을 지원하도록 개발됐다 CSI를 사용하면 자체 스토리지용 드라이버를 만들어서 k8s와 사용할 수 있다.

Container Networking Inferface

다양한 네트워킹 솔루션에 대해 지원을 확장하기 위해 CNI가 있다.

185: Volumes

docker와 마찬가지로 k8s에서 생성된 파드는 본질적으로 일시적이다. 그래서 영속성을 가지게 하기 위해 pod에 볼륨을 연결한다.

Volume Storage Options

볼륨에는 스토리지가 필요하다.

  • local, AWS, NFS, GCP 등등

186: Persistent Volumes

볼륨에 대한 스토리지를 구성하는 데 필요한 모든 configuration 정보는 파드 정의 파일 내에 있다. 대규모 환경의 경우 매번 def 내에 스토리지를 구성해야하고 변경사항 있을때마다 모든 파드에 변경 사항을 적용해야할 것 이다. 따라서 스토리지 또한 좀 더 단체로 중앙에서 관리하고 싶을 것이다

관리자가 대규모 스토리지 풀을 생성하고 사용자가 필요에 따라 풀을 분할해 구성할 수 있다. 이것이 persistent volume이 나타난 이유이다.

  • PV는 클러스터에 애플리케이션을 배포하는 유저가 사용하도록 관리자가 구성한 cluster-wide 스토리지 볼륨 풀이다
  • 사용자는 Persistent Volume Claims(PVC)를 사용해 이 풀에서 스토리지를 선택할 수 있다

PV 생성

apiVersion: v1 kind: PersistentVolume metadata: name: pv-vol1 spec: capacity: storage: 5Gi accessModes: [“ReadWriteOnce”] # ReadOnlyMany, ReadWriteOnce, ReadWriteMany hostPath: path: /tmp/data

생성

  • $ kubectl create -f pv-definition.yaml 조회
  • $ kubectl get pv

187: Persistent Volume Claims

Persistent Volume을 만들었다면 이제 노드에서 스토리지를 사용할 수 있도록 Persistent Volume Claims를 만들어보자 이 둘은 별개 오브젝트이다.

  • 관리자는 Persistent Volume들을 생성한다
  • 사용자는 스토리지를 사용하기 위해 Persistent Volume Claims를 생성한다.
  • k8s는 request와 volume에 설정된 속성을 기반으로 persistent volume을 claims에 바인딩한다.

모든 PVC는 단일 PV에 바인딩된다. 바인딩 과정에서 k8s는 claims에서 요청한 대로 용량이 충분한 PV를 찾는다.

PVC 생성

apiVersion: v1 kind: PersistentVolumeClaim metadata: name: myclaim spec: accessModes: [“ReadWriteOnce”] # ReadOnlyMany, ReadWriteOnce, ReadWriteMany resources: requests: storage: 5Gi

생성

  • $ kubectl create -f pvc-definition.yaml 조회
  • $ kubectl get pvc

PVC에서 50Mi를 요청해도 연결한 PV가 100Mi가 있다면 CAPACITY에 100Mi로 집계된다

+PV에 persistentVolumeReclaimPolicy 옵션을 retain으로 둔다면 연결된 PVC를 삭제해도 terminating에서 행걸리고 삭제되지 않는다. 즉, PVC가 남아있으나 unavailable한 상황이 된다. 연결된 POD를 삭제하면 그제서야 PVC가 지워진다. 그리고 PV는 Released 상태가 된다

188: Using PVCs in PODs

PVC를 생성하면 볼륨 섹션의 persistenceVolumeClaim 섹션 아래 PVC 클레임 이름을 지정해 POD 정의 파일에서 PVC를 사용한다.

이는 ReplicaSet Deployment도 마찬가지다

apiVersion: v1 kind: Pod metadata: name: mypod spec: containers: - name: myfrontend image: nginx volumeMounts: - mountPath: “/var/www/html” name: mypd volumes:

  • name: mypd persistentVolumeClaim: claimName: myclaim

+pod 에 접속해 명령어 날리는 법

  • $ k exec webapp – cat /log/app.log

+++volumeMounts를 활용한 볼륨으로 애플리케이션 구성하는건 시험에 나온다

193: Storage Class

이전에는 PV를 생성하고 PVC로 해당 스토리지를 요청한 다음 Pod definition에서 PVC를 volume으로 사용하도록 했다. gcp를 이용해서 pvc 만들 수도 있다

Static Provisioning

  • 정적 프로비저닝이란 뭔가 동적으로, 자동으로 안되는 것이다.
  • PV를 생성하기 전에 Google CLoud에 디스크를 생성해야 한다.
  • 즉, 애플리케이션에 스토리지가 필요할 때마다 먼저 GCP에서 만들고 연동해야한다. 이를 static provisioning volume이라고 한다.

Dynamic Provisioning

애플리케이션이 필요로할 때 GCP에서 어련히 volume을 자동으로 프로비져닝해주면 좋지 않겠는가 Storage Class를 이용하면 Google Cloud에서 스토리지를 자동으로 프로비저닝하고 claim이 있을 때만 파드에 연결할 수 있는 프로비져너(예: Google Storage)를 정의할 수 있다.

  • Sotrage Class object 정의서를 만든다
  • PVC 정의문에 storageClassName이 신규로 생겼다. 이를 기입해준다.
  • Storage Class가 있으므로 PV 정의가 이제 필요하지 않다
  • PVC가 생성되면 연결된 스토리지 클래스는 정의된 provisioner를 이용해 GCP에서 필요한만큼의 새 디스크를 프로비져닝하고 Persistent Volume(PV)를 생성하고 PVC를 해당 volume에 바인딩한다. 따라서 여전히 PV를 생성하는 건 같다. 다만 더이상 수동으로 PV를 생성해줄 필요가 없다는 것이다.

다양한 프로비져너가 있고 parameters로 type과 replication-type 을 줄 수 있다

조회

  • $ kubectl get storageclasses

196: Networking - Section Introduction

시스템에서 IP 주소 설정 및 확인에 대한 기본 사항, 게이트웨이 및 경로에 대한 지식, DNS와 서버 구성 등 k8s의 CNI가 POD 네트워킹 문제를 어떻게 해결하는지 확인하자 클러스터 DNS와 k8s DNS에 대해 다루고 ingress 네트워킹으로 마무리한다

198: Prerequisite - Switching Routing

Networking Pre-Requisites

  • 스위칭, 라우팅, 게이트웨이 등과 같은 기본 네트워킹 개념을 살핀다.
  • 이후 DNS와 CoreDNS에 대한 이해
  • Linux에서의 DNS 구성을 보자

Switching

두 컴퓨터는 스위치에 연결되어 네트워크를 생성한다.

  • 스위치에 연결하려면 물리적 또는 가상의 각 호스트에 인터페이스가 필요하다)
  • 스위치에 연결할 eth0 이라는 인터페이스가 있고 주소가 192.168.1.0인 네트워크라고 가정하자
  • 동일 네트워크에 있는 IP 주소로 시스템을 할당하고 그러기 위해 “ip addr” 커맨드를 활용한다
    • $ ip addr add 192.168.1.10/24 dev eth0 # 서버 1에서
    • $ ip addr add 192.168.1.11/24 dev eth0 # 서버 2에서
  • 링크가 작동하고 IP 주소가 할당되면 이제 컴퓨터들은 스위치를 통해 서로 통신할 수 있다.
  • 네트워크 호스트에서 패킷을 수신해 동일한 네트워크 내의 다른 시스템으로 전달할 수 있다.

Routing

192.168.2.0에 시스템 C와 D가 있다고 가정하자. 한 네트워크 시스템이 다른 네트워크 시스템에 어떻게 도달할 수 있을까? 직전에 본 192.168.1.11이 어떻게 IP 192.168.2.10에 닿을 수 있을까? -> 라우터가 필요하다

라우터는 두 네트워크를 연결해준다. 두 개의 개별 네트워크에 연결되므로각 네트워크에 하나씩 두 개의 IP가 할당된다.

Gateway

시스템 B에서 C로 패킷을 보내려할 때 라우터는 네트워크에서 패킷을 보낼 위치를 어떻게 알 수 있을까? 각 시스템들이 모인 네트워크가 방이라면 게이트웨이는 다른 네트워크나 인터넷 등 외부 세계로 통하는 문이다. 문은 어디로 가야 하는지 알아야 한다

  • $ route
  • 위 명령어로 커널의 라우팅 테이블을 알 수 있다. (routing config가 없다면 다른 시스템에 도달할 수 없는 것)

ip route add 커맨드로 가능하다

  • $ ip route add 192.168.2.0/24 via 192.168.1.1 (2번 시스템에서)
  • $ ip route add 192.168.1.1/24 via 192.168.2.0 (1번 시스템에서)
  • 서로의 네트워크를 연결한다.

+외부 인터넷으로 갈 때는 0.0.0.0 혹은 default로 보내고 받는 라우팅을 추가하면 된다.

+호스트가 인터페이스간 패킷을 전달할 수 있는지 여부는 파일 “proc/sys/net/ipv4/ip_forward”에서 시스템 설정에 따라 결정된다. default로 이 값은 0이며 전달이 없음을 의미한다. 이것을 1로 설정하면 핑이 통과한다

명령어 요약

  • ip link: 호스트의 수정 인터페이스 나열
  • ip addr: 인터페이스에 할당된 IP 주소 확인
  • ip addr add: 인터페이스에 IP 주소 설정
  • ip route 혹은 route: 라우팅 테이블 확인
  • ip route add target via : 라우팅 테이블에 항목 추가
  • cat /proc/sys/net/ipv4/ip_forward: 라우터로 구성된 호스트로 작업하는 경우 호스트에서 IP 전달이 활성화돼있는지 확인

199: Prerequisite - DNS

Name Resolution

  • A가 B를 “db”라고 부르고 싶다
  • /etc/hosts에 매핑한다
  • ping db 를 통해 원하느 별칭으로 부를 수 있게 되었다 호스트 이름을 IP 주소로 변환하는 것을 name resolution이라고 한다.

DNS

각 시스템에서 다른 모든 시스템을 매핑해 /etc/hosts에 저장해 놓는건 소수 시스템으로 구성된 작은 네트워크에서는 가능하다. 서버 중 하나의 IP가 변경되면 다른 모든 서버들에서 항목을 수정해야하니 비효율적이다. 그래서 모든 항목을 중앙에서 관리할 단일 서버를 두는 방법을 사용함.

이름을 발견할 때마다 DNS 서버에 조회해 IP를 받아낼 수 있다. 로컬 /etc/hosts 혹은 DNS 에서 찾는다

Domain Names

web, db, nfs 같은 단순 string으로 시스템을 읽으려했으나 www.facebook.com 은 어떡할까?

  • 입력 app.google.com 요청이 들어온다
  • root DNS 서버는 .com 을 제공하는 DNS 서버로 토스한다
  • google을 아는 DNS 서버에 다시 전달한다.
  • app을 아는 상위 DNS 서버에 전달해 정확한 ipv4를 알아낸다

Record Types

  • A: 호스트 이름에 IPv4를 저장하는 것
  • AAAA: 호스트 이름에 IPv6을 저장하는 것
  • CNAME: 이름을 다른 이름에 매핑하는 것 str2str

201: Prerequisite - Network Namespaces

Linux의 Namespace에 대하여. 네트워크 namespace는 docker같은 컨테이너에서의 네트워크 격리를 구현하는 데 사용된다.

Process Namespace

호스트가 집이라면 namespace는 각 자녀에게 할당하는 집 안의 private한 방이다. 각 방의 자녀들은 방 안의 영역에서 일어나는 일만 알 수 있고 호스트는 모든 방의 영역을 알 수 있다. namespace를 사용해 호스트에 특별한 공간을 만드는 것이다.

  • 컨테이너 안에서는 본인밖에 못보지만 host는 다 볼 수 있다.

Network Namespace

호스트에는 로컬 네트워크에 연결하는 자체 인터페이스가 있다. 호스트는 나머지 네트워크에 대한 정보가 포함된 자체 라우팅 및 ARP 테이블이 있다. 컨테이너는 생성되면 namespace 내에서 자체 가상 인터페이스, 라우팅 및 ARP 테이블을 가지게 된다.

Create Network Namespace

Exec in Network Namespace

  • ip link로 내 호스트의 인터페이스를 조회한다
  • 네트워크 namespace 안에서 명령어를 실행한다
  • red 네트워크 namespace 안에서는 호스트의 80 인터페이스(eth0)은 볼 수 없는 것을 알 수 있다.

+ARP와 Routing Table 모두 마찬가지이다.

Virtual Cable (veth)

두 네트워크 네임스페이스들을 이어주기 위해 가상 이더넷 쌍을 이용한다.

  • veth(가상 이더넷)으로 엮는다
  • 각 인터페이스를 namespace에 연결한다
    • $ ip link set veth-red netns red
  • ip를 할당한다
    • $ ip -n red addr add 192.168.15.1 dev veth-red
  • ns 내 각 장치에 대해 인터페이스를 불러온다
    • $ ip -n red link set veth-red up
  • 링크가 작동하여 ns가 서로 연결할 수 있다.

  • red ns에서 ping 날려 blue ns로 시도하면 정상적으로 통신된다
  • ARP 테이블을 보면 둘의 mac 주소와 ip 그리고 인터페이스가 잘 나타남을 알 수 있다.
  • 그리고 이는 호스트가 전혀 모르는 내부적인 네트워크의 인터페이스 사항이다.

203: Prerequisite - Docker Networking

도커의 네트워킹에 대해 알아보자

$ docker run –network option

  1. None
    • $ docker run –network none nginx
    • 도커 컨테이너는 이경우 네트워크에 연결되지 않는다.
    • 컨테이너는 외부로 접근하지 않고 외부의 누구도 컨테이너에 도달할 수 없다.
  2. host
    • $ docker run –network host nginx
    • 컨테이너가 호스트 네트워크에 연결되고 호스트와 컨테이너간 네트워크 분리가 없다
    • 네트워크를 공유하고 포트도 같이 쓴다.
    • 컨테이너에서 localhost로 서버를 띄우면 서버 IP로 뜬다
  3. bridge
    • $ docker run nginx
    • docker 호스트와 컨테이너들을 연결하는 내부 전용 네트워크가 생성된다.
    • 이 네트워크에는 기본주소 172.17.0.0이 있고 연결된 장치들은 하위 주소들을 얻는다

bridge가 가장 중요하므로 도커가 이 네트워크를 어떻게 관리하는지 알아보자

  • 새 컨테이너가 생성된다
  • 도커는 namespace를 만들고 인터페이스 쌍을 만든다
  • 인터페이스 한쪽 끝은 컨테이너에 다른 쪽 끝은 브리지 네트워크에 연결한다

포트 매핑 또한 필요하다.

  • 호스트 ip의 8080으로 접근했을 때 내부 컨테이너의 80 포트로 연결되게 할 수 있다
  • $ docker run -p 8080:80 nginx
  • 도커 호스트의 8080 포트에 대한 트래픽은 컨테이너 포트 80에 전달된다

204: Prerequisite - CNI (Container Network Interface)

컨테이너 네트워킹 인터페이스에 대해 알아보자 컨테이너를 bridge 네트워크에 연결하기 위해 필요한 모든 작업르 수행하는 프로그램을 만들었다. 이러한 bridge 프로그램은 컨테이너 런타임 환경이 이런 작업에서 벗어나게 하고 나머지 부분들을 처리해준다. Rocket이나 k8s가 새 컨테이너를 만들 때 bridge program을 호출하고 컨테이너 ID와 namespace를 전달하여 해당 컨테이너에 대한 네트워킹을 구성한다.

CNI는 컨테이너 런타임 환경에서 네트워킹 문제를 해결하기 위해 프로그램을 개발하는 방법을 정의한 표준의 집합이다. 앞서 말한 Bridge 프로그램은 CNI를 위한 플러그인이다. CNI는 플러그인이 어떻게 개발돼야하는지, container runtime이 호출을 어떻게 해야하는지 정의한다.

205: Cluster Networking

k8s 클러스터의 마스터 노드와 워커 노드에 필요한 네트워킹 configuration을 살펴보자.

  • 각 노드에는 네트워크에 연결된 인터페이스가 하나 이상 있어야 한다
  • 각 인터페이스에는 주소가 있어야 한다.
  • 고유한 호스트 이름 집합과 고유한 MAC 주소가 있어야 한다

Port

  • 마스터 노드는 API서버에 대해 6443 포트 연결을 허용해야한다
  • kubelet을 위한 포트 10250을 열어 두어야 한다.
  • kube scheduler를 위해 10259 포트도 열려있어야 한다
  • kube controller manager를 위한 10257 포드 또한 열려있어야 한다.
  • 마지막으로 etcd를 위해 2379 포트를 개방해야한다

Networking practice 1 - Explore Environment

  • 클러스터에서 노드가 다른 노드에 연결될 수 있는 Network Interface는 어떻게 알 수 있나? (node-to-node communication)
  • $ ip link 혹은 ip a 커맨드를 통해 인터페이스들을 볼 수 있다. (ip a == ip addr)
  • $ k get nodes -o wide 를 통해 internal-ip 를 보면 해당 ip가 link된 인터페이스를 알 수 있고 그것이 외부와 연결된 것이다

+현재 노드의 인터페이스에서의 MAC 주소는 어떻게 알 수 있나?

  • 위에서 찾은 인터페이스에 명시돼있다
  • $ ip link show eth0

+다른 노드의 MAC address는 어떻게 알까? 해당 노드에 ssh로 접근해 들어가 $ ip link show eth0

+Containerd를 container runtime으로 사용할 때 현재 노드에서 Containerd가 만든 inferface/bridge는 무엇인가?

  • $ ip link 명령어 했을 때 cni0, br-* 인 것이 있으면 해당 인터페이스이다
  • docker0은 Docker에서 생성한 기본 브리지 네트워크이다. Docker 컨테이너들이 기본적으로 이 네트워크를 통해 통신한다.
  • flannel은 네트워크 플러그인으로 주로 k8s pod끼리 네트워크 구성할 때 사용한다
  • cni0은 CNI 네트웤 ㅡ플러그인의 가상 브리지이다. containerd는 CNI 플러그인을 이용해 네트워크를 설정한다.

+controlplane에서 Google에 ping 하고 싶은 경우 어떤 라우터가 이를 맡는가? default gateway의 IP 주소는 무엇인가?

  • $ ip route show default 명령어를 통해 알 수 있다
  • -> default via 172.25.0.1 dev eth1

+controlplane 노드에서 kube-scheduler가 listening 중인 포트는 몇번인가?

  • $ netstat -nplt 명령어로 알 수 있다. 사용중인 모든 포트에 대한 ip address 들이 나온다

209: Pod Networking

k8s는 모든 파드가 고유한 IP 주소를 가지며 IP 주소로 노드 내 다른 모든 파드에 도달 할 수 있다.

210: CNI in Kubernetes

CNI는 CNI container runtime에 따른 각자의 책임을 정의한다.

Configuring CNI

k8s가 사용할 CNI 플러그인을 어디에 지정해야할까? 클러스터의 각 노드에 있는 kubelet.service에서 구성된다.

  • kubelet.service 를 확인하거나
  • 실행중인 정보 확인
    • $ ps -auxgrep kubelet
  • bridge 파일 찾기
    • cat /etc/cni/net.d/10-bridge.conf

Networking practice 2 - CNI

+kubelet service를 검사해서 k8s에 container runtime endpoint value가 정의됐는지 확인하라

  • $ ps -auxgrep kubeletgrep runtime 명령어의 결과에 있다
  • –container-runtime-endpoint=unix:///var/run/containerd/containerd.sock

+CNI supported 플러그인들의 binary 파일들을 모아놓은 경로는 /opt/cni/bin 이다

+k8s 클러스터를 구성하는데 사용된 CNI plugin은?

  • $ ls /etc/cni/net.d/ 의 결과를 기입한다
  • 10-flannel.conflist .kubernetes-cni-keep

+컨테이너와 관련 namespace가 생성된 뒤 kubelet이 어떤 이진 파일을 실행할 것인가?

  • $ cat /etc/cni/net.d/10-flannel.conflist 을 통해 확인해보자
  • flannel

Networking practice 3 - Deploy Network Solution

+설치되있는 “weave-net” 네트워크 솔루션을 클러스터에 설치하기

  • $ k create -f /root/weave/weave-daemonset-k8s.yaml

Networking practice 4 - Networking Weave

+이 클러스터의 network solution은 무엇인가?

  • $ ls /etc/cni/net.d 결과로 나오는 파일명에 명시돼있다

+몇개의 weave agents/peers 가 이 클러스터에 배포돼있는가?

  • weave는 pod으로 표현되므로
  • $ k get pods -n kube-system 에 포착된다

+weave에 의해 만들어진 bridge network는

  • $ ip a 혹은 ip link를 통해 알 수 있다

+node01에서 스케쥴링된 pod의 default gateway는 어디인가?

  • ssh node01 간 뒤 $ ip route 하면 나오는 정보를 보라
  • default router는 외부로 나가는 것이다.
  • weave 가 나오는 정보가 내부 pod을 컨트롤하는 것

Networking practice 5 - Service Networking

+이 클러스터의 pods 이 가질 수 있는 IP 범위는 어디인가

  • CNI 확인
    • $ cat /etc/cni/net.d/ 이하 파일명 확인
  • 인터페이스 확인
    • $ ip a
    • 찾은 CNI 범위 확인

+이 클러스터의 서비스가 가질 수 있는 IP 범위는 어디인가

  • $ cat /etc/kubernetes/manifests/kube-apiserver.yamlgrep cluster-ip-range

+kube-proxy 구성될 때 사용된 proxy 종류는?

  • 로그를 통해 직접 확인해야함
  • $ kubectl logs kube-proxy-pod-name -n kube-system
  • -> I1006 05:48:58.506869 1 server_linux.go:69] “Using iptables proxy”

222: DNS in k8s

클러스터 내에서 파드, 서비스와 서로 다른 컴포넌트 간의 DNS 확인에 대해 다룬다 k8s는 클러스터 설정할 때 default로 built-in DNS 서버를 배포한다

서비스가 생성될 때마다 k8s DNS 서비스는 서비스를 위한 레코드를 생성한다. 서비스 이름을 IP 주소에 매핑하므로 클러스터 내에서 모든 파드가 해당 서비스 이름을 활용해 이 서비스에 도달할 수 있다.

namespace 내의 모든 사람은 이름으로만 서로를 부르고 다른 namespace 사람을 부를 땐 전체 이름을 사용한다 DNS 서버도 마찬가지이다

223: CoreDNS in k8s

매번 통신을 위해 서로 다른 IP들을 /etc의 host 파일에 지정하고 일일히 모든 컴퓨터들에서 수정하고 추가해줄 수 없는 노릇이다. 중앙 DNS 서버에서 일괄적으로 관리하는 방식을 취한다 coreDNS 서버는 k8s 클러스터의 kube-system namespace에서 pod로 배포된다

  • /etc/coredns 에 위치한 core file을 사용한다

Networking practice 6 - CoreDNS

+CoreDNS에 접근하기 위해 만들어진 서비스의 이름은 무엇인가?

  • $ kubectl get service -n kube-system
  • kube-dns 라는 것이 보인다

226: Ingress

service와 ingress의 차이는 자주 꼽히는 어려운 점이다. 차이점 위주로 비교하며 알아보자

서비스 flow

  1. clusterIP 제품을 판매하는 온라인 매장이 있는 회사를 위해 Kubernetes에 애플리케이션을 배포한다. 예를 들어 myonlinestore.com에서 사용할 수 있다. 애플리케이션을 Docker 이미지로 빌드하고 Kubernetes 클러스터에 Deployment의 Pod로 배포. db로는 MySQL 데이터베이스를 파드로 배포하고 mysql-service이라는 클러스터 IP 유형의 서비스를 생성하여 애플리케이션에 액세스할 수 있게 하고 이제 애플리케이션이 작동한다.
  2. NodePort 외부 접근을 위해 노드 포트 유형 서비스를 만들고 클러스터의 노드에 있는 high 포트에서 애플리케이션을 사용할 수 있게 한다. 이 예에서는 포트 38080이 서비스에 할당된다. 이제 사용자는 포트 38080 다음에 오는 노드의 URL \HTTP://IP를 사용하여 애플리케이션에 액세스할 수 있다. 해당 설정이 작동하고 사용자가 애플리케이션에 액세스할 수 있다. 트래픽이 증가할 때마다 추가 트래픽을 처리하기 위해 파드의 replicas 수를 늘리고 파드 간에 트래픽을 분할하는 서비스를 처리한다
  3. 그 이상 유저가 ip를 입력해서 페이지에 접근하는걸 원하지 않으므로 dns 서버를 구성한다.
    • 이제 유저는 dns 이름 (myonlinestore.com)과 포트번호를 이용해 접근할 수 있다 포트번호도 유저가 입력하지 않아도 되게 하고싶다.
    • 하지만 Node Port 서비스는 30000이상의 높은 번호대 포트만 할당할 수 있다
    • 따라서 DNS서버와 클러스터 사이에 proxy 서버를 추가 계층으로 둬서 80포트 트래픽을 (http default) 38080 포트로 매핑하게 한다
    • DNS 서버에 포트 없이 mystore.com 으로 등록해도 된다!

추가적인 확장성과 관리를 위해서 ingress가 필요한 것

  • 단일 url 기반으로 다양한 각각의 load balancer 와 같은 라우팅 설정 필요성
  • 산재된 각종 configuration
  • ssl http설정 위해서 각 endpoint를 관리해야함

Ingress

Ingress는 사용자가 URL 경로를 기반으로 클러스터 내의 다른 서비스로 라우팅하도록 구성할 수 있는 외부에서 액세스 가능한 단일 URL을 사용하여 애플리케이션에 액세스할 수 있도록 도우며 동시에 SSL 보안 구현. 간단히 말해 Ingress는 Kubernetes의 다른 오브젝트와 마찬가지로 builtin Kubernetes 프리미티브를 사용하여 구성할 수 있는 Kubernetes 클러스터에 builtin된 레이어 7 로드 밸런서이다

물론 Ingress를 사용하더라도 클러스터 외부에서 액세스할 수 있도록 expose해야 한다. 따라서 여전히 노드 포트로 게시하거나 클라우드 네이티브 로드 밸런서를 사용하여 publish해야 하지만 이는 처음 한번만 구성하면 되는 configration이다. 앞으로 Ingress 컨트롤러에서 모든 로드 밸런싱 authentication, SSL, URL 기반 라우팅 configuration을 수행하게 된다.

그렇다면 ingress는 어떻게 로드밸런싱을 수행하며 ssl은 어떻게 관리하고 관련 요소들을 configuration할까?

  • 역방향 프록시나 NGINX, HAProxy 또는 Traefik과 같은 로드 밸런싱 솔루션을 사용하는데 이들을 k8s 클러스터에 구성해 트래픽을 다른 서비스로 라우팅하게 한다
  • configuration에는 URL route 정의, SSL 인증서 configuration이 있다

Ingress Controller

  • nginx를 비롯한 로드밸런싱 솔루션을 먼저 배포한다 Ingress resources
  • 기타 여러 사항들을 구성한다

k8s는 Ingress controller를 기본으로 제공하지 않는다. built-in돼있지 않다.

Ingress Controller

k8s가 기본 built-in ingress controller를 제공하지 않는다. 우린 어떤걸 선택해 사용해야할까?

HTTP 로드 밸런서인 Google의 레이어 7인 GCE 그리고 NGINX, Contour, HAProxy, Traefik, Astel 등등이 있다. 이 중 GCE와 NGINX는 현재 쿠버네티스 프로젝트에서 지원 및 유지 관리하는 솔루션이다.

  • 이들 ingress controller는 단순한 로드 밸런서가 아니다.
  • 로드 밸런싱은 컴포넌트 중 하나이고 모니터링을 비롯한 다른 것들도 포함돼있다

Nginx Ingress Controller 예시

ingress controller는 k8s에서 또 다른 deployment로 배포된다. 따라서 간단한 replicaset 그리고 pod definition이 적힌 파일을 만들어야한다

nginx ingress controller라는 이미지명을 입력한다. 이것은 Kubernetes에서 ingress 컨트롤러로 사용하도록 특별히 제작된 NGINX의 특수 빌드이므로 고유한 요구 사항이 있다. 우선 커맨드로 실행하는 명령을 내려야한다.

  • 로그 저장 위치
  • keep alive time
  • ssl설정 및 세션 타임아웃 위 설정들은 configmap으로 구성해두고 커맨드의 다음 매개변수로 전달하면 된다

또한 파드 이름과 namespace를 환경변수로 전달해야한다. 추가로는 http와 https 포트를 제공한다 이후 ingress controller를 외부에 expose하기 위해 NGINX Ingress 레이블 selector로 Node Port 유형의 서비스를 만든다. 그리고 ingress 리소스를 모니터링하고 수행하려면 여러 권한이 인가된 서비스 계정이 필요하다.

요약하면 다음 것들이 필요하다

  • NGINX ingress 이미지 배포
  • expose하는 서비스
  • NGINX configuration 데이터를 제공하는 config map
  • 모든 오브젝트에 접근할 수 있도록 올바른 역할이 있는 서비스 계정 준비

Ingress Resources

ingress resource는 ingress controller에 적용되는 rule 및 configuration 집합이다. 모든 트래픽을 단일 애플리케이션으로 전달하거나 URL 기반으로 트래픽을 다른 애플리케이션으로 라우팅하도록 rule을 구성할 수 있다.

backend 섹션은 트래픽이 라우팅되는 타겟을 정의한다. 그래서 단일 backend인 경우 rule이 없다. 서비스 이름과 포트만 지정하면 된다. 생성

  • $ kubectl create -f ingress-wear.yaml 확인
  • $ kubectl get ingress

  • describe을 통해 확인해 볼 수 있다.
  • default backend가 default로 보인다면 ingress controller의 설정을 확인해봐야 한다.
    • $ kubectl get deploy deploy-name -o yaml 혹은 describe 해보면 알 수 있다.

각각 다른 namespace에 있는 service, deployment를 배포할 때면 해당 namespace 안에 ingress를 만들어 주어야 한다. 그래도 같은 도메인의 url 안에서 동작하게 할 수 있다.

URL 경로 기반 처리

우리는 myonlinestore.com 으로 들어오는 모든 트래픽을 처리하고 URL 경로 기반으로 라우팅하는 것이다.

도메인 이름 기반 처리

+Ingress에 변경사항이 있다 이전 버젼과 현재 버젼에서 차이점이 있다.

k8s 1.20+ 버젼에서는 imperative 방식으로도 Ingress resource를 생성할 수 있다.

  • 포맷) $ kubectl create ingress ingress-name —rule=“host/path=\service:port”
  • 예시) $ kubectl create ingress ingress-test —rule=“wear.my-online-store.com/wear*=wear-service:80”

230: Ingress - Annotations and rewrite-target

Ingress 컨트롤러에는 작동 방식을 지정할 수 있는 다양한 사용자 옵션이 있다. NGINX Ingress 컨트롤러에는 많은 옵션이 있고 이번에는 Rewrite target 옵션을 다뤄볼 것이다.

기본적으로 웹 서비스 pod들을 씌우면 다음과 같다. watch앱은 비디오 스트리밍 웹페이지를 \http://(watch-service):port/ 에서 보여주고 wear앱은 의류 웹페이지를 \http://(wear-service):(port)/ 에서 보여준다.

하나의 서비스 도메인 페이지에 맨 뒤에 /watch, /wear 이런 식으로 라우팅하려면 Ingress를 사용해야한다. \http://(ingress-service):(ingress-port)/watch 여기로 들어온 트래픽을 \http://(watch-service):(port)/ 여기로 라우팅해야한다. 그리고 이렇게 할 수 있도록 Ingress Controller에서 구성하는 것이다.

그런데 만약 rewrite-target 옵션이 없으면 URL 끝에 watch, wear를 붙여야 한다. \http://(ingress-service):(ingress-port)/watch \http:/(watch-service):(port)/ watch 그러나 watch service는 그자체로 watch 서비스이므로 보통 마지막에 /watch를 추가로 붙일 필요가 없다.

그래서 re-write option은 relace(path, rewrite-target) 의 기능을 한다 apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ingress-example annotations: nginx.ingress.kubernetes.io/rewrite-target: /$2 spec: rules: - http: paths: - path: / pathType: Prefix backend: service: name: service-one port: number: 80 -path: /something(/|$)(.*) pathType: Prefix backend: service: name: service-two port: number: 80

/something/A/B 로 요청이 왔을 때 2번째 캡처 그룹인 (.*) 즉 /A/B를 변수로 이용해 A/B로 라우팅한다 /something/A 로 요청이 온다면 A 로 라우팅한다.

물론 첫 규칙에 의해 /something 이 없는 주소 /abc 등은 service-one으로 요청된다

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.