Skip to content

Latest commit

 

History

History
221 lines (160 loc) · 14.5 KB

8-networking.md

File metadata and controls

221 lines (160 loc) · 14.5 KB

Networking

Linux Networking Basics

어떤 A, B 시스템이 있다면 어떻게 네트워크로 연결될 수 있을까?
Switch는 여러 시스템을 하나의 네트워크로 연결해주는 역할을 한다. 이 시스템에서 스위치를 통해 연결되기 위해서 반드시 인터페이스가 필요하다.
아래 명령어를 사용해서 네트워크 인터페이스 연결 여부를 확인할 수 있다.

ip link

그리고 ip addr 명령어를 사용하면 연결된 네트워크 인터페이스에 할당된 IP 주소를 확인할 수 있다.

ip addr add 192.168.1.10/24 dev eth0

만약 192.168.1.0/24 네트워크와 192.168.2.0/24 네트워크가 존재한다면, 이 네트워크는 서로 다른 네트워크를 의미하기 때문에 직접적으로 통신할 수 없다.
Router는 서로 다른 네트워크를 연결해주는 역할을 한다.
그래서 Router에는 각 다른 네트워크를 통해 서로 IP를 하나씩 할당 받게 된다. (192.168.1.1, 192.168.2.1)

Gateway는 다른 네트워크나 인터넷으로 나가기 위한 관문 역할을 한다. 시스템은 이 문을 통해 어디로 나가야할 지 결정하는 라우팅 설정을 갖고 있다.
route 명령어를 통해 커널 라우팅 테이블을 확인할 수 있다.
만약 192.168.1.11에서 192.168.2.10으로 패킷을 보내려 한다면, 다음과 같이 ip route명령어로 Gateway를 지정할 수 있다.

ip route add 192.168.2.0/24 via 192.168.1.1

인터넷 상에 있는 네트워크(예: 172.217.194.0)에 연결하기 위해서도 동일한 과정으로 라우팅 설정을 하면된다.
어떤 다른 네트워크에 대해 기본 Gateway를 통해 나가게 하려면 아래와 같이 default 설정을 한다.

ip route add default via 192.168.2.1

default 설정 시 default 항목을 0.0.0.0으로 지정하면 동일한 의미를 갖는다.
Router가 여러개일 경우 각 Router마다 Gateway를 위한 IP를 설정하고, 라우팅 설정에서 목적지 네트워크 주소(예: default 또는 다른 네트워크 대역)를 지정하면 된다.

A 호스트가 C 호스트와 통신하기 위해서 우선 B 호스트로 연결되는 Gateway 설정을 해야 한다.

# 예시
ip route add 192.168.2.0/24 via 192.168.1.6

요청을 받은 C 호스트가 A호스트에게 응답을 전달하려면 위 설정과 마찬가지로 Gateway 설정이 필요하다.
하지만 실제로 Ping이 도달하지 않는 현상이 발생한다.
리눅스는 기본적으로 하나의 인터페이스로 들어온 패킷을 다른 인터페이스로 전달되지 않는다. 위 경우에는 B 호스트의 eth0로 수신된 패킷은 eth1을 통해 다른 곳으로 전달되지 않는다. 그 이유는 보안적인 이유 때문이다.
만약 B 호스트에 연결된 네트워크가 Private과 Public이라면, 악의적인 누군가가 Public 네트워크의 접근을 통해 Private 네트워크로 접근할 수 있기 때문이다.
하지만 이 경우에는 Private 네트워크간 중계를 담당하기 때문에 이러한 보안 설정을 임의로 조정할 수 있다.
/proc/sys/net/ipv4/ip_forward 파일의 숫자가 기본값이 0이지만 이 값을 1로 변경하면 된다. 이 설정은 시스템 재부팅시 초기화 되므로 /etc/sysctl.conf 파일도 수정해줘야 한다.

DNS

/etc/resolv.conf에 네임 서버를 명시할 수 있고, search 항목에 탑 레벨 도메인을 명시한다.
해당 호스트에서 어떤 호스트를 찾기 위한 질의를 할 때 search에 명시된 탑 레벨 도메인의 하위 서브 도메인인지 먼저 찾아보게 된다.

Network Namespace in Linux

리눅스 커널 네임스페이스는 프로세스와 네트워크를 격리 시킬 수 있는 기능이다.
네임스페이스 내에서 실행되는 프로세스는 PID 1을 가지면 root 권한으로 실행된다. 이 프로세스는 실제로 호스트 환경에서도 확인할 수 있지만 PID 1이 아닌 다른 PID를 갖고 있는 것을 확인할 수 있다.

네임스페이스를 통해 네트워크를 구성하기 위해서는 아래 명령어를 사용한다.

ip netns add red

호스트에서 네트워크 인터페이스를 확인할 수 있는 명렁어인 ip link를 네임스페이스의 네트워크에서도 실행할 수 있다.

ip netns exec red ip link

# 위 명령어와 동일한 기능을 한다.
ip -n red link

네임스페이스 상의 네트워크는 lo <LOOPBACK> 인터페이스만 확인되며, 호스트의 eth0인터페이스는 볼 수 없다.
따라서 네임스페이스 상의 네트워크는 인터넷에 연결될 수 없다.
인터넷에 연결하기 위해서는 Virtual Ethernet Pair를 이용해야 한다.
이것은 종종 Pipe와 관련되어 취급되지만 두 인터페이스를 Virtual Cable로 연결된 것처럼 생각하면 된다.
케이블을 만들기 위해서 link 명령어의 peer를 이용해서 ......

TODO: 강좌를 다시 듣고 자세히 정리할 것

Docker Network

none네트워크 : 어떤 네트워크와도 연결되지 않는다. 다른 컨테이너끼리 통신도 되지 않는다.
host 네트워크 : 호스트 네트워크를 공유한다. 단, 여러 컨테이너가 같은 포트를 사용할 수는 없다.
bridge 네트워크 : 내부 Private 네트워크를 만든 후 호스트 네트워크와 연결된다.

이전 강좌를 완전히 이해한 후 다시 볼 것

Container Network Interface

CNI는 컨테이너 런타임 환경에서 네트워킹 문제를 해결하기 위해 프로그램을 개발하는 방법을 정의하는 표준 집합이다.

Container Networking Interface (CNI) in Kubernetes

CNI는 컨테이너 런타임 환경에서 서로 네트워킹 문제를 해결하기 위해 프로그램을 개발하는 방법을 정의한 표준이다.

서로 다른 노드의 POD끼리 통신을 위해서 각 노드 환경마다 Bridge 네트워크와 라우팅 테이블을 준비해야 하는 작업을 해야한다.
이 작업들은 각 단계별로 필요한 단계를 수행하는 스크립트를 작성해서 각 컨테이너가 실행될 때마다 수동으로 실행되도록 해야 했다. 하지만 큰 서비스 환경에는 이 같은 수동 작업은 많은 리소스 낭비가 발생한다.

그래서 쿠버네티스의 POD가 실행될 때 자동으로 이런 스크립트를 실행 시켜줄 수 있는 중간자 역할을 하는 것이 CNI이다.
CNI는 쿠버네티스가 컨테이너를 생성하자마자 이 스크립트를 호출하는 방법을 알려주는 식이다.

kubelet은 컨테이너 생성을 담당하는 역할을 수행한다.
kubelet이 컨테이너를 생성될 때마다 kubelet 프로세스 실행시 전달 받은 CNI 설정 정보(--network-plugin=cni)를 통해 실행 가능한 CNI 플러그인 파일(--cni-bin-dir)과 설정 파일(--cni-conf-dir)을 찾는다 그리고 스크립트의 add명령어를 수행하게 되는 것이다.

CNI Weave Works

라우팅 테이블은 많은 엔트리를 포함할 수 없기 때문에 다른 해결책이 필요하다.
CNI 플러그인이 클러스터에 배포되면 모든 노드에 에이전트처럼 배포된다. 이 에이전트들은 서로 통신해서 노드와 네트워크, POD에 대한 정보를 교환한다.
네트워크 패킷이 어떤 POD에서 다른 노드로 전달될 때 Weave가 이 패킷을 가로챈 뒤 목적지 POD가 존재하는 노드의 네트워크 주소 정보를 포함 시켜 다시 캡슐화 해서 해당 노드로 전달한다. 그러면 해당 노드의 Weave가 다시 이 패킷을 받아 캡슐화된 정보를 확인해서 실제 목적지 POD에서 최종 전달하게 된다.

IPAM (IP Address Management)

IP를 어떻게 할당할지 이 정보는 어디에 저장하고 중복되지 않은 IP를 누구에게 할당할 지를 역할을 수행한다.

CNI Plugin Responsibilities

  • Must support argument ADD/DEL/CHECK
  • Must support parameters container id, network namespace, etc...
  • Must manage IP Address assignment to PODs
  • Must Return results in a specific format

Service Networking

Service가 생성되면 어떤 POD가 있는 노드이건 관계없이 클러스터의 모든 부분에서 액세스 할 수 있다. 그리고 POD와 달리 특정 노드에서 고정되어 실행되는 것이 아니다. 하지만 클러스터 내부에서만 접근할 수 있는 특징이 있다. 이같은 특징을 가진 형태를 보통 ClusterIP 타입의 Service이 된다.

그렇다면 Service는 어떻게 IP 주소를 할당 받고 클러스터 내 모든 노드에서 접근할 수 있는 것일까?
그리고 외부 사용자가 각 노드의 특정 포트를 통해 Service의 접근할 수 있는 방법은 무엇인가?

각 노드에는 kube-proxy 라는 컴포넌트가 실행되고 있다. kube-proxy는 kube-apiserver를 통해 클러스터의 변경 사항을 지켜보고 있고, 주기적으로 새로운 Service가 생성되었는지 확인한다.

Service는 POD와 달리 각 노드에 할당되거나 생성되지 않는다.
Service는 클러스터 전역에 걸쳐있는 컨셉이며, 클러스터 내 모든 노드에서 존재한다고 볼 수 있다.
하지만 사실 Service는 실제 존재는 것이 아니다. 이것은 IP를 갖지만 요청을 기다리는 어떤 서비스나 서버가 아니다. 단지 가상의 오브젝트일 뿐이다.

쿠버네티스에서 Service를 만들면 미리 정해진 범위 내에서 IP 주소를 할당받는다.
kube-proxy 컴포넌트는 모든 노드에서 실행되고 있고, 그 Service의 IP 주소 가져와서 노드의 포워딩 규칙을 만든다. 그래서 이 Service의 IP로 트래픽이 들어오게 될 때 다시 적절한 POD의 IP로 전달될 수 있는 것이다.
따라서 클러스터 내 어떤 POD에서든 Service의 IP를 통해 다른 POD로 접근할 수 있다.

kube-proxy는 userspace, iptables, ipvs 3가지 프록시 모드를 지원한다. 기본은 iptables다.

kube-proxy --proxy-mode [userspace | iptables | ipvs]

kubectl get service를 통해 나오는 결과에 표시된 CLUSTER-IP 대역은 kube-api-server를 실행할 때 전달한 --service-cluster-ip-range파라미터의 값이다. 기본값은 10.0.0.0/24다.

kube-proxy는 rule을 iptables에 등록한다.

iptables -L -t net | grep db-service

DNS

쿠버네티스 클러스터에 기본적으로 kube-dns가 있으며, Service와 POD의 목적지 IP에 대한 FQDN을 관리하고 있다.
Service의 경우 {Service Name}.{Namespace}.svc.cluster.local형식의 도메인을 가지며, POD는 {POD IP의 점을 대시로 변경}.{Namespace}.svc.cluster.local 형태다.

CoreDNS in Kubernetes

모든 POD는 생성될 때 DNS 서버의 IP가 명시된 /etc/resolv.conf가 추가된다.
쿠버네티스 버전 1.12부터 CoreDNS를 추천하고 있다.
CoreDNS는 kube-system 네임스페이스에 Deployment(POD)로 배포된다.
CoreDNS 설정파일은 Corefile에 정의하고 /etc/coredns 경로에 있다.
Corefile내용 중 kubernetes속성이 쿠버네티스 플러그인 설정이다.

.:53 {
  errors
  health
  kubernetes cluster.local in-addr.arpa ip6.arpa {
    pods insecure
    upstream
    fallthrough in-addr.arpa ip6.arpa
  }
}

cluster.local은 Top 레벨 도메인을 지정한 것이며, pods설정은 POD에 대한 레코드 설정을 의미한다.
Corefilekube-system 네임스페이스에 Configmap 오브젝트에 저장되어 있다. 설정을 변경하려면 이 Configmap을 수정하면 된다.

각 POD의 /etc/resolv.conf에는 DNS 서버의 IP가 입력되어 있는데 CoreDNS도 여러 POD로 실행되기 때문에 직접 접근하기 보다 Service를 이용해야 한다. 쿠버네티스 클러스터는 기본적으로 kube-dns라는 Service를 생성해준다.
이 Service의 IP는 kubelet실행 설정에 지정되어 있다. (/var/lib/kubelet/config.yaml에서 clusterDNS값)

CoreDNS로 도메인을 조회할 때 FQDN으로 조회하는 원리는 각 POD의 /etc/resolv.confsearch옵션을 통해 가능하다.

Ingress in Kubernetes

Ingress는 사용자가 외부에서 액세스 할 수있는 단일 URL을 사용하여 애플리케이션에 액세스하게 해준다. 이 URL은 URL Path를 기반으로 클러스터 내의 다른 서비스로 라우팅하도록 구성 할 수 있다. 동시에 TLS 인증서 보안도 제공한다.

Ingress는 쿠버네티스 클러스터에 장착된 L7 로드밸런서라고 생각해도 된다.
Ingress를 실행하기 위해서는 외부 네트워크에서 들어올 수 있는 NodePort나 Load Balancer 타입의 Serivce가 필요하다. 이것은 단 한 번만 하면 된다.

만약 쿠버네티스가 밖에서 Ingress 역할을 직접 만든다고 하면 Nginx, HAProxy, Traefik 같은 솔루션을 이용해야 한다.
쿠버네티스에는 이 부분을 __Ingress Controller__라고 부르는 리소스를 생성할 수 있다. 그리고 그 설정들을 __Ingress Resource__라고 하며, POD, Deployments, Services 같이 Definition file을 사용해서 생성한다.

Ingress Resource가 작동하려면, 클러스터는 실행 중인 Ingress Controller가 반드시 필요하다.
하나의 클러스터 내에 여러 개의 인그레스 컨트롤러를 배포할 수 있다. 인그레스를 생성할 때, 클러스터 내에 둘 이상의 인그레스 컨트롤러가 존재하는 경우 어떤 인그레스 컨트롤러를 사용해야 하는지 표시해주는 적절한 ingress.class 어노테이션을 각각의 인그레스에 달아야 한다.

쿠버네티스는 IngressController를 기본적으로 설정하지 않기 때문에 직접 배포해야 한다.
Ingress Controller를 직접 배포하기 위해서 사용할 수 있는 솔루션은 GCE Load Balancer, Nginx, Contour, HAProxy, Traefik, Istio 등이 있다.
현재 쿠버네티스 프로젝트에서 GCE와 Nginx를 지속적으로 지원하고 유지하는 상태다.

먼저 Ingress Controller를 Deployments로 배포해야 한다.

Ingress Resource는 쿠버네티스 Definition 파일로 생성한다.
Ingress에 정의된 Backend는 POD를 직접 지정하지 않고, Service를 지정한다.

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress-wear
spec:
  backend:
    serviceName: wear-service
    servicePort: 80
kubectl create -f Ingress-wear.yaml

kubectl get ingress