CKA 대비 kubernetes 스터디 - 8. Networking (2)
Networking Cluster Nodes
- k8s cluster는 여러 개의 노드 - 마스터 노드, 워커 노드 - 가 network interface로 연결되어 있는 형태.
- 각각의 노드에는 ip address가 매핑되어 있어야 하고, unique hostName이 있어야 하며, 고유한 Mac Address 값이 있어야 한다.
- 기존 VM을 복사해서 노드를 생성할 때 특히 유의할 부분임.
- kubernetes 컴포넌트를 실행하고, 컴포넌트 간 통신을 위해 열려 있어야 하는 포트들.
Pod Networking
컨테이너 단위로 통신하고, 컨테이너와 호스트를 연결하는 작업 외에도 k8s에서는 pod layer Networking이 있다.
- pod 주소는 어떻게 할당되며, 통신은 어떻게 할 것인가?
- 클러스터 내에 있는 service를 사용해서 internal 통신, 외부 통신은 어떻게 동작하도록 만들 것인가?
가 시험에 나옴.
- k8s에서 제공하는 빌트인 패키지가 따로 있는 건 아니고, networking solution을 사용해서 해결하면 된다.
- k8s가 pod networking에서 요구하는 사항은 명확하다.
- 모든 pod는 고유한 ip주소가 있어야 한다. (range / subnet 여부에 관계없이)
- 같은 노드에 있는 pod, 다른 노드에 있는 pod과 NAT 없이 통신이 가능해야 한다.
이 기능을 제공해주는 솔루션 (ex. cilium)도 많지만, 이전까지 배운 내용을 토대로 직접 세팅해볼 예정.
같은 말을 반복하는 것 같지만, 그만큼 중요하다는 걸로 이해하면 된다.
- 각 노드에 bridge network를 생성한 뒤,
ip link set dev v-net-0 up
과 같은 명령어로 network를 실행한다. - 각 personal network는 subnet을 가지고 있어야 한다. 따라서 bridge network의 subnet range를 설정해주자.
- 예시의 경우 10.244.1.0/24 처럼 24 range를 지정했다.
ip addr add 10.244.1.1/24 dev v-net-0
- 예시의 경우 10.244.1.0/24 처럼 24 range를 지정했다.
아래의 작업은 컨테이너를 생성할 때마다 실행해야 하는 작업들이다. 따라서 스크립트를 만드는 편이 효율적이다.
- virtual Cable 생성하기.
ip link add ...
- container / bridge에 각각 연결한다.
ip link set ...
- ip주소를 할당하고, route에 default gateway를 설정한다.
ip -n <ns> addr add ...
ip -n <ns> route add ...
- ip주소 정보는 어디서 얻어와서 할당해야 하나? -> 우리가 직접 관리하거나, DB에 저장해두는 식으로.
- 예시의 경우 10.244.1.1 / 10.244.2.1 / 10.244.3.1 (free ip in subnet)
ip -n <ns> link set ...
로 인터페이스를 호스트와 연결한다.
이제 같은 노드 안에서 pod끼리 통신은 가능해졌다. 다른 노드에 있는 pod과 통신하기 위해서는
- 각 노드별로 route의 업데이트를 일일이 다 해주거나.
- default gateway - network routing 정보를 매핑하는 table을 router에서 관리하거나. [X]
이 과정까지 끝나면, 다른 노드에 있는 pod에도 접근할 수 있는 Large Network가 생성된다.
CNI는 지금까지 manual하게 실행한 과정에서
- net-script.sh 라는 스크립트로 컨테이너가 실행될 때 네트워크 세팅을 자동화하는 영역을 의미한다.
- 위에서 설명한 영역은 ADD 메소드가 담당해야 할 부분이고
ip link del ...
명령어들의 집합인 DEL 메소드,
container를 생성하는 kubelet에서
- 실행 시점에서
--cni-conf-dir=/etc/cni/net.d
옵션에 정의된 경로에서 CNI configuration을 찾고, 스크립트 이름을 확인한다. --cni-bin-dir=/etc/cni/bin
옵션값에 설정된 디렉토리에서 sh파일을 찾는다.- ADD command를 실행한다. 명령어의 옵션값으로 < container 이름>, < namespace> 를 넣고 실행한다.
CNI in Kubernetes
- CNI 옵션 확인하는 방법 1. 각 노드의 kubelet.service file에서 옵션값 확인하기
ps -aux | grep kubelet
명령어로 kubelet 프로세스에서 옵션 확인하기.- /opt/cni/bin 디렉토리에서 supported cni plugin을 확인할 수 있다.
- /etc/cni/net.d 에서 configuration file 값을 확인할 수 있다. kubelet에서 '어떤 플러그인을 사용해야 할지' 확인하는 경로.
- /etc/cni/net.d 에서 볼 수 있는 파일의 예시.
- 이전에 cli로 세팅했던 bridging, routing, masquerading 등의 옵션이 설정되어 있다.
CNI Solution: WeaveWorks
Routing table 사용해서 연결하는 작업은 클러스터 규모가 커지면 한계를 맞이함. 테이블 크기를 무한정 늘릴 수 있지도 않고, 조회에 걸리는 시간도 점점 늘어남.
Solution의 도입. WeaveWork를 예시로 들면
- 각 노드마다 네트워크를 담당하는 weavework service가 생성되고, 이 pod끼리는 서로 통신하면서 서로의 위치를 안다.
- pod에서 다른 pod로 요청을 보낼 때, 패킷을 중간에서 가져간 뒤 weavework 노드에서 인식할 수 있도록 wrapping한다.
- 목적지에 도달했을 때, 해당 노드에 있는 weavework service가 unwrapping을 담당한다.
- weaveworks service는 자체적인 bridge network를 생성한다.
- pod는 multiple bridge에 붙을 수 있다 (docker에서 생성한 bridge / weavework에서 생성한 bridge 두 개에 전부 붙을 수 있음)
- pod의 요청이 들어오면, weavework는 'destination 노드에 있는 weavework service'로 요청을 전달하는 역할을 수행한다.
- packet을 intercept -> 다른 노드에 있는 pod로 보내는 요청인지 확인
- encapsulate packet with a new one, with a new source / destination
- 목적지에 도착하면, weavework service에서 패킷을 decapsulate / route to the right pod.
DaemonSet 형태로 설치됨 -> 모든 node에 반드시 하나의 pod는 존재하도록.
IPAM - IP Address Management (CNI)
- 노드 내의 virtual network / ip subnet은 어떻게 결정되고 누가 관리하는가?
- pod는 어떻게 ip주소가 할당되는가?
- ip주소 정보는 어디에 저장되고, 중복이 없는지는 누가 체크하는가?
CNI 정의에 따르면 assigning IP to the container는 CNI Plugin이 담당해야 한다. kubernetes가 관리하는 영역이 아님.
단순하게 생각할 수 있는 방법 중 하나로, 노드의 available IP주소를 각 노드에 저장해서 사용하는 방법이 있다.
- 노드마다 ip available list를 저장해두고
- ip를 할당하는 스크립트에서 free ip를 찾아서 할당하는 식.
- free ip를 찾아서 할당하는 방법으로 built-in plugin이 제공된다. DHCP와 host-local 두 가지.
- 위 예시그림의 경우, host-local 방식을 사용해서 할당한 것.
- 어쨌든 이 방식으로는 script에서 DHCP이건 host-local이건 사용자가 직접 invoke를 해줘야 한다.
사용자가 직접 invoke하는 대신, different kinds of plugin을 사용할 수 있도록 동적으로 스크립트를 생성할 수 있다.
cat /etc/cni/net.d/net-script.conf
파일의 IPAM 필드에서 확인할 수 있다.- subnet / routes 정보를 제공하면, 해당 플러그인 (DHCP / host-local)을 사용해서 ip 할당을 진행한다.
3rd party 프로덕트의 경우 방법이 좀 다를 수 있다. Weavework를 예로 들면
- weave는 전체 네트워크에서 ip주소로 할당하는 디폴트 값의 범위가
10.32.0.0/12
이다. - 이 범위는 1,048,574개의 ip주소를 생성할 수 있고, 이 중 특정 range의 ip주소가 각 노드에 배정된다. 해당 노드의 subnet으로 pod의 ip주소를 할당하는 식으로 동작함.
Service Networking
지금까지 pod에 ip주소를 할당했지만, 실제로 pod가 다른 pod의 ip에 직접 접근하는 경우는 잘 없다.
- 보통 service라는 k8s object를 사용해서 접근함.
ClusterIP
특정 노드 하나에 배포되지만, 클러스터 단위로 service에 접근이 가능함. 클러스터 외부에서는 접근 불가능한 형태의 service.
- ex) 오렌지색 pod가 클러스터 외부에서는 접근 불가능한 DB 서비스일 경우 오랜지색 service는 ClusterIP.
NodePort
클러스터 내부에서도 접근 가능하고, 클러스터 외부에서도 접근할 수 있는 service.
학습할 내용
- Service는 어떻게 ip를 할당받는지
- 어떻게 service는 여러 노드에서 접근할 수 있는 구조로 되어 있는지
- 어떻게 노드와 포트를 사용해서 외부 사용자가 접근할 수 있도록 만들 수 있는지
- 위 작업은 누가 담당하고, 어떻게 수행하며, 어디서 확인할 수 있는지?
기본적으로 pod를 생성하는 역할은 각 노드의 Kubelet이 담당한다. - kube-apiserver에게서 요청을 받으면 pod와 service를 생성한다. - 이 과정에서 CNI를 사용하고, configuring network 과정도 진행된다.
각 노드에 있는 kube-proxy
- kube-apiserver에게서 요청을 받는 컴포넌트
- 노드에 새 pod 또는 service가 생성되면 동작한다.
기본적으로 service는 cluster-wide 한 개념이지만, 정확히는 'There is no server or service really listening on the IP of the service'.
- service에는 pod와 달리 networking interface / namespace / process 와 같은 개념이 존재하지 않는다.
- Virtual Object
Service 오브젝트를 생성하면, "ip address와 port 조합" 하나가 주어진다.
- 각 노드의 kube-proxy는 이 ip주소+port (service에 할당된 듯 보이는 ip주소) 와 실제 pod을 연결하는 forwarding rules를 생성한다.
- 이 주소로 들어오는 모든 트래픽은 해당 노드의 대응되는 Pod으로 전부 보내는 것.
kube-proxy가 이 forwarding rule을 생성하는 방법엔 여러 가지가 있다.
- userspace : kube-proxy listens on a port
- ipvs : proxies connections to the pods by rules
- iptable : 디폴트 옵션이자 가장 익숙한 방법
--proxy-mode 옵션으로 설정해줘야 하며, 설정하지 않을 경우 iptable이 기본으로 설정된다.
- Service를 생성했을 때 할당되는 Ip주소는 kube-api-server의 --service-cluster-ip-range 값에 따라 결정된다.
ps aux | grep kube-api-server
로 확인할 수 있음- 디폴트 범위는 10.96.0.0 ~ 10.111.255.255
- cf. pod의 범위는 10.244.0.0 ~ 10.244.255.255
- 범위가 어떻게 설정되건, 절대 겹치지 않는다는 게 핵심.
iptable은 아래 명령어로 확인할 수 있다.
iptable -L -t nat | grep db-service
- 결과를 보면 10.103.132.104:3306 으로 오는 요청은 전부 10.244.1.2:3306 으로 포워딩하는 걸 볼 수 있다.
- NodePort의 경우도 동일한 방식으로 iptable을 설정한다.
- iptable을 생성할 때 kube-proxy 자체적으로도 로그를 남기는데, 로그 경로는 installation에 따라 다를 수 있다.
- 강의의 경우 로그는
cat /var/log/kube-proxy.log
에 저장됨.
DNS in kubernetes
k8s 클러스터 내부에서 사용하는 built-in DNS Server가 존재한다. (설치를 바닥부터 직접 했다면, 이것도 직접 설치해야 한다)
- service가 생성될 때, DNS server는 해당 service의 이름과 ip주소를 저장한다.
- 이 값이 있다면, 클러스터 내에서는 service 이름만 있어도 해당 ip에 접근이 가능하다.
- 같은 Namespace에 있다면, service 이름만으로도 통신이 가능함.
- 서로 다른 namespace에 있다면, DNS server는 해당 namespace로 subdomain을 생성한다.
- 위 예시의 경우, default의 pod가 app에 있는 web-service에 접근하려면
http://web-service.app
으로 접근해야 한다. namespace가 subdomain이 되는 셈. - namespace 말고도, 모든 service는 svc라는 또다른 subdomain으로 그룹화된다.
- 위 예시의 경우, default의 pod가 app에 있는 web-service에 접근하려면
정확한 FQDN은 service-name.namespace.type.root -> ip address로 연결된다.
- service일 경우 svc라는 type으로 그룹화되며, root는 디폴트값이 cluster.local이다.
- 따라서 기본 옵션에서의 FQDN은
web-service.apps.svc.cluster.local
- pod의 경우 ip주소의 dot을 dash로 변경하면 hostName이 된다.
CoreDNS - how kubernetes implements DNS?
DNS 서버가 왜 등장했는지를 설명할 때, 각 서버 (또는 pod)에서 직접 /etc/hosts를 관리하기 어렵기 때문이라고 했다.
- kubernetes의 DNS도 이 원리에서 크게 벗어나지 않는다. 단지 pod가 ephemeral이라서 pod주소를 직접 관리하지 않을 뿐.
- pod 대신 service를 관리한다고 보면 된다.
k8s 1.12버전부터는 CoreDNS를 기본으로 사용함.
- CoreDNS는 kube-system Namespace에 pod 형태로 생성된다.
- redundency를 위해 Deployment로 두 개의 pod가 만들어져 있음.
cat /etc/coredns/Corefile
에 configuration 값이 기록되어 있다.- 주황색으로 표시된 것들이 plugins. error / health check, monitoring metrics, cache 등이 있다.
- 이 중 coreDNS를 kubernetes에서 구동할 수 있게 해주는 것이 kubernetes plugin. -> top level of Domain name이 만들어지는 지점.
- 예시의 경우 cluster.local 값이 확인된다. 이게 root 값이 됨.
- pods 옵션 : what is responsible for creating a record for Pods in cluster. pod ip주소를 변형해서 hostName 생성하는 걸 말하는데, 디폴트는 disabled.
- proxy : 만약 pod가 google.com 같이 DNS 외부로 연결을 시도할 경우,
/etc/resolv.conf
파일에 정의되어 있는 namespace로 연결해준다.
- 이 configuration file은 kube-system ns에 configmap 형태로도 저장되어 있다. 따라서 옵션값을 변경하려면 configmap을 수정하면 된다.
CoreDNS pod도 다른 pod 요청을 받기 위해 service를 생성한다.
- kube-dns라는 이름의 service로, kube-system namespace에 만들어져 있다.
- 각 pod는 처음 생성될 때
/etc/resolv.conf
파일에 kube-dns의 service ip주소가 nameserver로 등록되어 있다. - 이런 사전작업은 전부 kubelet 이 담당한다.
그러면, web-service 라는 이름만 갖고 FQDN을 어떻게 찾아가는 걸까?
- nameserver 말고 search로 아래 세 개의 도메인을 추가해뒀기 때문.
default.svc.cluster.local
svc.cluster.local
cluster.local
- 따라서 service의 경우에는 serviceName만으로도 FQDN을 만들 수 있다.
- 만약 pod의 hostName만으로 DNS 서버에 요청을 보낼 경우 제대로 찾아내지 못함. FQDN을 제대로 입력해야 ip주소를 확인할 수 있다.
Ingress
service
- nodeport 형태로 생성해서 http;//node-ip:38080 과 같은 형태로 외부 트래픽을 받을 수 있게 해 줌.
- 트래픽 증가로 pod 개수가 많아질 경우 load balancing 수행.
하지만 실 서비스의 경우
- node ip를 직접 연결하는 대신 DNS를 통해 사용자가 접근할 수 있도록 변경한다.
- port 30000 이상의 번호를 직접 쓰는 대신, 80포트를 사용하는 프록시 서버를 사용하는 편.
- 만약 on-prem이 아니라 AWS, GCP의 경우 service의 타입을 LoadBalancer로 설정할 수 있다.
- 이 경우 Cloud Provider가 loadbalancer를 붙여준다.
여기서 새로운 앱이 만들어졌고, 같은 도메인을 공유한 채 path만 다르게 해서 사용한다고 가정하자.
- ex) wear: http://domain.com/wear, video : http://domain.com/watch 같은 형태
- 새로운 service (loadbalancer or nodeport) 를 생성하면 별도의 service ip가 만들어진다.
- 여러 개의 service로 들어가는 트래픽을 적절히 split 하는 another load balancer가 필요해짐.
- another load balancer에 ssl 통신을 활성화하면 https를 사용할 수도 있음.
Ingress
- end user가 외부에 공개된 하나의 url로 접근했을 때, path 기준으로 같은 클러스터 내에 있는 different service에 routing해주는 역할을 담당한다.
- ssl Security를 담당한다.
k8s에서 제공하는 Layer 7 LoadBalancer라고 이해하면 됨.
- 물론 ingress도 하나의 service인 만큼 nodeport / loadbalancer 설정을 해줘야하지만, 여기에서만 외부 연결에 관련된 설정을 해주면 됨.
- loadbalancing, ssl, url based routing 같은 역할을 ingress controller가 담당한다.
- 리버스 프록시를 가능하게 하는 솔루션 - nginx, haproxy 등 - 을 배포한 다음, 이 솔루션이 적용할 configuration setting을 만들어준다.
- 여기서 nginx같은 솔루션을 ingress controller, 해당 솔루션의 configuration을 ingress resources라고 한다.
- ingress resources는 definition file (yaml file)로 만들어진다.
- ingress controller는 k8s의 built-in으로 제공되지 않는다.
ingress Controller
ingress controller로 사용 가능한 솔루션들. GCE와 nginx는 k8s 프로젝트에서 maintain중인 솔루션들이다.
k8s에서 제공하는 ingress-controller 이미지를 Deployment로 사용할 때
- args는 반드시
/nginx-ingress-controller
를 붙여야 한다. - nginx의 config option (error log path, keep-alive option, ssl-protocol 같은)은 configmap에 별도로 생성한다.
- blank object로도 충분하다고 함. 만들어두는 편이 나중에 값을 수정할 때 용이해서 만드는 거라고 함.
--configmap=$(POD_NAMESPACE)/nginx-configuration
옵션을 넣어준다.
- deployment가 사용하는 환경변수 - pod_name, pod_namespace - 를 등록해준다.
- nginx가 사용할 포트인 80, 443도 등록해준다.
nginx controller가 외부로부터 요청을 받을 수 있는 service를 NodePort로 생성한다.
- service의 port를 설정하고, 어느 deployment에 붙을 것인지 selector로 지정한다.
- nginx controller는 필요에 따라 k8s object를 관리할 수 있는 권한이 필요하다. 따라서 이 권한을 위한 ServiceAccount를 생성해준다.
- nginx-ingress-serviceaccount 라는 이름으로 만들고, 적절한 role / clusterRole / RoleBinding 으로 권한을 부여한다.
다시말해
- layer 7 loadBalancer 역할을 할 nginx-ingress controller (Deployment)
- controller가 외부로 노출될 수 있게 하는 service
- configuration을 위한 configmap
- 동작에 필요한 k8s 권한을 설정하는 Auth (serviceAccount)
가 기본 형태.
ingress resources
Set of rules / configurations applied on ingressController.
- 예컨대 모든 요청을 한 종류의 deployment (pod)로 전부 forward하거나
- routing traffic based on url path / domain name itself
spec.backend로 이 ingress가 forwarding할 서비스는 어디인지 설정할 수 있고, 포트도 지정할 수 있다.
kubectl create -f ingress-wear.yml
로 리소스 생성이 가능함.
resources로 rule을 지정할 수 있다.
- 특정 도메인을 기준으로 rule을 지정하는 게 가능함. 해당 rule에 맞게 특정 service를 호출할 수 있다.
- 예컨대 www.my-online-store.com 도메인의 path에 따라 접근해야 할 pod를 다르게 설정할 수 있음.
위 예시처럼 ingress에서 spec.rules를 사용해서 특정 path -> 특정 backend로 넘어가도록 세팅할 수 있음.
kubectl describe ingress ingress-wear-watch
로 ingress 설정을 확인할 수 있다.- default backend 항목: 아래의 rules에 전부 해당되지 않는 요청은 어디로 보낼 것인지를 결정하는 필드.
- 예시의 경우 default-http-backend 라는 service로 이동한다.
- 없는 path나 응답할 수 없는 request가 왔을 때 대응할 수 있는 적절한 메시지를 위한 용도로 사용하면 좋다.
- request url별로 (host별로) 설정하려면 spec.rules 하위항목에 host를 지정하면 된다.
- host를 지정하지 않았을 경우에는 기본적으로 asterisk (*), 즉 도메인 상관없이 들어오는 모든 트래픽에 적용된다.