Switching Routing
ip link서로 다른 Host 가 통신하기 위해선 두 Host 를 이어주는 Switch 가 필요하다. Host 를 Switch 에 연결하려면 Network Interface 가 필요한데, 위 명령어를 통해 Host 의 Network Interface 를 확인할 수 있다. Linux 의 경우 eth0 라는 Network Interface 를 확인할 수 있다.
ip addr
ip addr add 192.168.1.10/24 dev eth0Host 들이 Switch 에 연결된 이후 위 명령어를 통해 Host 에 IP 주소를 할당해줄 수 있다.
ip route
ip route add 192.168.2.0/24 via 192.168.1.1
ip route add default via 192.168.2.1
ip route add 0.0.0.0 via 192.168.2.1서로 다른 Switch 가 통신하기 위해선 두 Switch 를 이어주는 Router 가 필요하다. 192.168.2.0/24 의 주소를 가지는 Switch 에 접근하기 위해선 Router 의 192.168.1.1 주소를 거쳐가면 된다는 의미다. default 또는 0.0.0.0 는 이외 모든 주소를 의미하며 즉, 인터넷에 접근하기 위해선 192.168.2.1 주소를 거쳐가야 된다는 의미다.
cat /proc/sys/net/ipv4/ip_forward
echo 1 > /proc/sys/net/ipv4/ip_forwardRouter 가 192.168.1.1 와 192.168.2.1 의 주소를 가지고 있다는 것은 eth0 과 eth1 이 할당됐다는 의미이고, 기본적으로 서로 다른 Network Interface 간에 Packet 전달은 막혀있다. 위 명령어를 통해 ip_forward 값을 1 로 바꿔주면 Router 내부에서 Packet 를 Network Interface 간에 전달할 수 있도록 해줄 수 있다. 재부팅할 경우 설정값이 초기화 되기 때문에 /etc/sysctl.conf 에서 net.ipv4.ip_forward 값을 1 로 설정해주면 영구적으로 설정할 수도 있다.
DNS
cat > /etc/hosts
192.168.1.10 web
192.168.1.11 db
192.168.1.15 web-1
192.168.1.16 db-1
192.168.1.21 web-2
192.168.1.22 db-2IP 주소 대신 Name 으로 Host 를 식별하기 위해 위 경로에 IP 주소에 해당하는 Host Name 을 지정해 줄 수 있다. 대신 모든 Host 에 필요하기 때문에 관리하기가 어려워진다. 때문에 DNS 서버를 활용해 하나의 관리포인트로 통합한다.
cat /etc/resolv.confDNS 서버의 주소는 위 경로에 저장하여 사용할 수 있다. 8.8.8.8 은 Google 이 제공하는 DNS 서버다.
cat /etc/nsswitch.conf
...
hosts: files dns
...기본적으로 /etc/hosts 에서 주소를 찾고 없으면 /etc/resolv.conf 에서 주소를 찾는데, 위 설정파일에서 순서를 변경할 수도 있다.
Network Namespaces
ip netns add red
ip netns add blueContainer 가 namespace 를 통해 프로세스를 격리하듯 Network 역시 격리할 수 있다. 위 명령어는 red 와 blue 라는 이름을 가진 Network Namespace 를 생성한다는 의미다.
ip netns exec red ip link
ip -n red linkip link 명령어를 통해 Network Interface 를 확인했듯 Network Namespace 의 Network Interface 를 확인하기 위해선 위 명령어를 사용할 수 있다.
ip -n red arp
ip -n red routearp 와 route 역시 동일한 방법으로 확인할 수 있다.
Virtual Cable
ip link add veth-red type veth peer name veth-blue위 명령어로 가상 케이블을 생성하고,
ip link set veth-red netns red
ip link set veth-blue netns blue
ip -n red addr add 192.168.15.1/24 dev veth-red
ip -n blue addr add 192.168.15.2/24 dev veth-blue
ip -n red link set veth-red up
ip -n blue link set veth-blue up위 명령어로 Network Namespace 끼리 연결해줄 수 있다.
Linux Bridge
ip link add v-net-0 type bridge
ip link set dev v-net-0 upNetwork Namespace 가 많아질 경우 Switch 역할을 하는 Linux Bridge 를 활용할 수 있다.
ip link add veth-red type veth peer name veth-red-br
ip link set veth-red netns red
ip link set veth-red-br master v-net-0
ip -n red addr add 192.168.15.1 dev veth-red
ip -n red link set veth-red up이후 Network Namespace 와 Bridge 를 연결하기 위한 Virtual Cable 을 생성 및 할당해주고, IP 주소 도 할당해 준 뒤 활성화 해주자.
ip addr add 192.168.15.5/24 dev v-net-0위 명령어를 통해 Host 와 Bridge 를 연결해주자.
ip netns exec blue ip route add 192.168.1.0/24 via 192.168.15.5Namespace 가 외부 Host 와 통신하기 위해선 외부에 192.168.1.0/24 의 주소를 가지는 Switch 와 routing 설정을 해줘야한다. 위 명령어를 사용하면 192.168.15.5 의 주소를 가지는 v-net-0 을 통해 호스트의 eth0 를 거쳐 외부로 향하게 된다.
iptables -t nat -A PREROUTING --dport 80 --to-destination 192.168.15.2:80 -j DNAT외부 Host 가 다른 Host 의 Network Namespace 에 접근하기 위해선 위 명령어로 포트포워딩 해줘야 한다.
Docker Networking
ip link08. Docker Networking 에서 배운 bridge 가 바로 Linux Bridge 다. ip link 로 확인해보면 docker0 라는 이름을 가진 Network Interface 를 확인할 수 있다.
ip netnsDocker Container 를 실행하면 Network Namespace 도 마찬가지로 자동으로 생성된다.
iptables -nvL -t nat포트포워딩도 마찬가지. Docker Container 를 실행할 때 iptable 설정까지 자동으로 생성된다.
즉, Docker Container 를 생성할 때, 네트워크 측면에선 내부적으론 위에서 언급한 모든 작업들이 자동으로 수행되던 것이다…
Pod Networking
서로 다른 Node 에 위치한 Pod 간 통신을 활성화 하기 위해선 아래와 같은 작업이 필요하다.
ip link add v-net-0 type bridge
ip link set dev v-net-0 up
ip addr add 10.244.1.1/24 dev v-net-0먼저 각 Node 마다 Bridge 를 생성해준 뒤,
ip route add 10.244.2.2 via 192.168.1.12다른 Node 의 Bridge 로 향하는 Route 를 설정해준다. 혹은 라우터를 활용해 Routing 설정을 한 곳에서 관리하는 방법도 있겠다.
Container 가 생성될 때 마다 Node 에 하나하나 접속해서 위 명령어들을 실행해야한다. Node 가 많아지고 네트워킹이 복잡해지면 해당 작업이 번거로워지기 때문에 자동화된 스크립트를 활용할 수 있다.
# net-script.sh
ADD)
# Create veth pair
# Attach veth pair
# Assign IP Address
# Bring Up Interface
DEL)
# Delete veth pair위와 같은 기준으로 작성된 스크립트를 활용해 Container 가 생성될 때 마다 실행해주면 Pod 간 네트워킹을 자동화할 수 있다. 위 기준을 CNI 가 제공하고 다양한 CNI Plugin 들이 구현된 스크립트를 제공한다.
Cluster Networking
K8s 도 결국 Master 와 Worker Node 간의 통신이 가능해야 함으로 각 Component 마다 위와 같은 Port 들을 열어줘야 한다. 자세한 내용은 공식 문서를 참고하자.
CNI
쿠버네티스는 기본적으로 Pod 간의 통신을 지원하지 않는다. 때문에 Pod 간 통신이 가능하도록 하려면 CNI Plugin 을 추가적으로 설치해 사용해야한다. CNI 는 쿠버네티스에서 Pod 에 IP 를 부여하고, 네트워크 연결을 실제로 구성해주는 핵심 네트워크 엔진이다.
ExecStart=/usr/local/bin/kubelet \
...
--network-plugin=cni \
--cni-bin-dir=/opt/cni/bin \
--cni-conf-dir=/etc/cni/net.d \
...CNI 를 설정해주기 위해선 kubelet 을 실행할 때 옵션으로 넘겨주면된다.
ls /opt/cni/bin위 경로에서 사용할 수 있는 모든 CNI Plugin 들을 확인할 수 있다.
ls /etc/cni/net.d위 경로에 존재하는 파일을 바탕으로 CNI Plugin 를 설정할 수 있다.
cat /etc/cni/net.d/10-flannel.conflist
{
"name": "cbr0",
"cniVersion": "0.3.1",
"plugins": [
{
"type": "flannel",
"delegate": {
"hairpinMode": true,
"isDefaultGateway": true
}
},
{
"type": "portmap",
"capabilities": {
"portMappings": true
}
}
]
}Weave CNI
kubectl apply -f "https://cloud.weave.works/k8s/net?k8s-version=$(kubectl version | base64 | tr -d '\n')"Weave 는 CNI Plugin 중 하나인 솔루션으로 위와 같이 Pod 로 생성해서 운영할 수 있다.
Weave CNI - IP Address Management
cat /etc/cni/net.d/net-script.conflist
{
"cniVersion": "0.2.0",
"name": "mynet",
"type": "net-script",
"bridge": "cni0",
"isGateway": true,
"ipMasq": true,
"ipam": {
"type": "host-local",
"subnet": "10.244.0.0/16",
"routes": [
{
"dst": "0.0.0.0/0"
}
]
}
}Pod 간 할당되는 IP 를 관리하기 위해 CNI 에선 DHCP 와 host-local 이라는 빌트인 플러그인을 제공한다. 해당 설정 역시 위 파일에서 관리할 수 있다.
NetworkPolicy
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: db-policy
spec:
podSelector:
matchLabels:
role: db # role=db 인 레이블을 가진 Pod 에 대한 NetworkPolicy
policyTypes:
- Ingress # Ingress 제한 (ingress 필드가 없으면 Ingress 를 모두 제한)
- Egress # Egress 제한 (egress 필드가 없으면 Egress 를 모두 제한)
ingress:
- from:
- podSelector: # 같은 네임스페이스의 role=api-pod 레이블을 가진 Pod 에서 오는 트래픽 허용
matchLabels:
role: api-pod
- namespaceSelector: # prod 네임스페이스의 모든 Pod 에서 오는 트래픽 허용
matchLabels:
name: prod
- ipBlock: # 192.168.5.10 에서 오는 트래픽 허용
cidr: 192.168.5.10/32
ports: # 위 from 에 해당하는 곳에서 TCP 3306 포트로 들어오는 트래픽만 허용
- protocol: TCP
port: 3306
egress:
- to:
- ipBlock: # 192.168.5.10 로 보내는 트래픽 허용
cidr: 192.168.5.10/32
ports: # 목적지(192.168.5.10)의 TCP 80 으로 나가는 트래픽 허용
- protocol: TCP
port: 80쿠버네티스는 기본적으로 클러스터에 속한 모든 Pod 간의 통신을 허용한다. 만약 Ingress 와 Egress 를 제한하고 싶다면 NetworkPolicy 를 생성하자.
트래픽 차단/허용은 CNI 가 구현하기 때문에 CNI Plugin 이 NetworkPolicy 를 지원해야 한다.
- Calico, Cilium 은 지원 / Flannel 은 미지원
- 지원하지 않는 CNI 환경에선
NetworkPolicy가 있어도 적용되지 않는다.
podSelector, namespaceSelector. ipBlock 등 다양한 기준으로 트래픽을 제어할 수 있다.
podSelector: 같은 namespace 내 특정 Pod 만 적용namespaceSelector: 특정 namespace 전체를 대상으로 적용ipBlock: 특정 IP 를 대상으로 적용
Service
Service 는 여러 개의 Pod 로 구성된 애플리케이션을 하나의 네트워크 엔드포인트로 외부에 노출시키는 리소스이다.
- Pod 는 언제든지 재시작되거나 재배포될 수 있기 때문에 IP 가 항상 바뀔 수 있다.
- Service 는 클러스터 내부에 고정된 가상 IP 를 외부에 노출시켜 클라이언트가 항상 같은 IP 주소로 파드에 접근할 수 있도록 한다.
- Label Selector 를 이용해 트래픽을 전달해야할 Pod 를 지정할 수 있다.
- 내부적으로 각 노드에서 실행되는
kube-proxy가 Service 의 가상 IP 로 들어온 요청을 Label Selector 조건에 해당하는 Pod 중 하나로 전달한다. - 여러가지 Service Type 을 가지고 있다.
- ClusterIP
- NodePort
- LoadBalancer 등
Service Networking
K8s 에선 Pod 끼리 통신하는 대신 Service 를 통해 Cluster 내 Pod 간의 통신 또는 외부와의 통신을 제공한다. Pod 이 namespace 등을 할당 받아 실제로 존재하는 것과 달리, Service 는 kube-proxy 에 의해 Cluster 에 걸쳐 Forwarding Rule 을 가지고 생성되는 Virtual Object 다.
kube-proxy --proxy-mode iptables # 'userspace | iptables | ipvs'kube-proxy 를 실행할 때 proxy-mode 옵션을 통해 Forwarding Rule 을 관리할 버전을 설정할 수 있다.
kube-api-server --service-cluster-ip-range ipNet (Default: 10.0.0.0/24)kube-api-server 를 실행할 때 Service 가 가질 수 있는 IP range 를 지정해줄 수 있다. Service IP range 와 Pod IP range 는 겹치면 안된다는 것도 참고하자.
iptables -L -t nat | grep db-serviceiptables 모드로 생성한 Service 의 Forwarding Rule 을 위 명령어를 통해 확인할 수 있다.
Service 흐름
클러스터 내부에서 Service 를 호출할 때:
- 클라이언트가 Service 의
ClusterIP:Port로 요청을 보낸다. - kube-proxy 가 이 요청을 가로챈다.
EndpointSlice를 참조해 적절한 Pod IP 로 라우팅한다.- Pod 는
targetPort에서 요청을 받는다.
ClusterIP
apiVersion: v1
kind: Service
metadata:
name: back-end
spec:
type: ClusterIP # default 값
selector: # label 기준으로 트래픽을 보낼 pod 결정
app: myapp
type: back-end
ports:
- port: 8080 # (required) 클라이언트가 Service 에 요청을 보낼 port
targetPort: 80 # 실제 pod 에서 리스닝하는 port, 지정하지 않으면 port 값을 따름ClusterIP 예시로 들면, back-end:8080 으로 들어온 요청이 label 이 app=myapp, type=back-end 인 Pod 의 80 포트로 전달된다.
- ClusterIP 는 클러스터 내부에서만 접근 가능한 IP 로 기본값으로 적용된다.
- 클러스터 내부에서 서비스 연결은 DNS 를 이용
- 가령 3-tier-architecture 에서 backend app 에 요청을 보내고 싶을 때 backend 를 담당하는 여러 Pod 가 cluster 전체에 퍼져있으니 ClusterIP 를 통해 원하는 layer 를 지정
NodePort
apiVersion: v1
kind: Service
metadata:
name: myapp-service
spec:
type: NodePort
selector: # label 기준으로 트래픽을 보낼 pod 결정
app: myapp
type: front-end
ports:
- port: 80 # (required) 클라이언트가 Service 에 요청을 보낼 port
targetPort: 80 # 실제 pod 에서 리스닝하는 port, 지정하지 않으면 port 값을 따름
nodePort: 30008 # 30000-32767 범위, 지정하지 않을 경우 자동으로 할당됨- 모든 노드에 동일한 고정 포트(30000-32767)를 생성하여 외부에 노출한다.
- 외부에서
{Node IP}:{nodePort}형식으로 접근할 수 있다. - NodePort 역시 ClusterIP 를 가지기 때문에 내부적으로는 ClusterIP 를 통해 Pod 에 접근한다.
kubectl run httpd --image=httpd --port=80 --expose
- kubectl run 으로 Pod 를 생성할 때 —expose 옵션을 주면 Pod 에 연결되는 ClusterIP 를 자동으로 생성해준다. ClusterIP 와 연결되어야 하기 때문에 —port 옵션이 필수적으로 포함되어야 한다.
LoadBalancer
apiVersion: v1
kind: Service
metadata:
name: myapp-service
spec:
type: LoadBalancer
ports:
- port: 80
targetPort: 80
nodePort: 30008- AWS, Azure, GCP 등에서 제공하는 Native Load Balancer 로 부하를 분산하여 보내는 역할을 수행한다.
- 내부적으로 NodePort 기반으로 작동한다.
ExternalName
apiVersion: v1
kind: Service
metadata:
name: myapp-service
spec:
type: ExternalName
externalName: myapp-service.example.com- 외부 DNS 이름으로 라우팅 CNAME 처럼 작동한다.
- 프록시가 아닌 DNS 레벨에서 리다이렉션
Service 생성
kubectl expose deployment nginx --port 80CoreDNS
<pod-ip-address>.<namespace-name>.pod.cluster.local
<service-name>.<namespace-name>.svc.cluster.localK8s 에서 Pod 과 Service 는 위 규칙에 따라 Domain Name 을 할당 받는다.
Pod 과 Service 들이 서로 Domain Name 으로 통신하기 위해 DNS 를 활용하는데, K8s 에선 CoreDNS 를 기반으로 한다. CoreDNS 는 기본적으로 Deployment 형태로 배포되며 Service 역시 제공해 Pod 과 Service 들이 kube-dns Service 를 향해 nslookup 을 수행할 수 있다.
kubectl get configmap -n kube-system
kubectl describe cm -n kube-systemCoreDNS 의 설정은 ConfigMap 으로 구성 가능하다.
cat /var/lib/kubelet/config.yaml | grep -A2 clusterDNS
clusterDNS:
- 10.96.0.10
clusterDomain: cluster.localCoreDNS 는 kubelet 으로 부터 배포되기에 kubelet 의 설정파일에서 CoreDNS 의 IP 를 확인할 수 있다.
kubectl run -it --rm --restart=Never test-pod --image=busybox -- cat /etc/resolv.conf
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
pod "test-pod" deletedPod 내부에 있는 resolv.conf 파일에서 CoreDNS IP 가 적용되어 있는 것을 확인할 수 있다.
CoreDNS vs ExternalDNS
CoreDNS 는 클러스터 내부의 DNS 질의를 처리하는 내부 DNS 서버이다. ExternalDNS 는 Ingress 나 Service 정보를 외부 DNS 제공자의 레코드를 자동으로 생성 및 동기화하는 컨트롤러이다.
CoreDNS
<service-name>.<namespace-name>.svc.cluster.local 등 클러스터 도메인에 DNS 질의를 담당한다. kube-system namespace 에 Deployment 와 관련 ConfigMap 으로 동작한다.
CoreDNS 의 질의 흐름은 아래와 같다.
- Pod 가 Service domain 으로 질의
- Pod 의
/etc/resolv.conf가 CoreDNS ClusterIP 를 name server 로 가리킴 - CoreDNS 의 kubernetes 플로그인이 Service endpoint 를 조회해 응답
- Cluster domain 이 아니면 forward 플러그인이 upstream 으로 포워딩
ExternalDNS
Ingress, Service, Gateway 등 리소스를 감시해 R53, Cloud DNS 등 외부 DNS 제공자에 A, AAAA, CNAME 등의 레코드를 생성한다. 즉, 외부인터넷에서 kubernetes cluster 로 접근할 때 필요한 domain name 들을 외부 DNS 와 연동해주는 역할을 한다.
ExternalDNS 의 동기화 흐름은 아래와 같다.
- 컨트롤러가 Ingress, Service, Gateway 등 리소스를 감시
- 호스트명 또는 주석을 읽어 목표 레코드를 계산
- 소유권을 관리하는 TXT 레코드 확인
- DNS 제공자 API 로 A, AAAA, CNAME 등을 생성 및 갱신
Ingress
Ingress 란
쿠버네티스에서 외부 트래픽을 받기 위해선 NodePort 또는 LoadBalancer 타입의 Service 를 생성해야 한다. Service 가 많아질수록 각 Service 마다 LoadBalancer 를 만들면 비용이 증가하고, 여러 개의 NodePort 를 관리하기 복잡해지고, 도메인/경로 기반 라우팅이 불가능하다는 문제가 생긴다.
이를 해결하기 위해 Ingress 라는 리소스를 사용한다. Ingress 는 쿠버네티스 내부로 진입하는 단일 진입점 역할을 수행하며 다양한 Service 로의 트래픽을 구분하고 도메인 또는 경로별로 라우팅하는 일종의 Load Balancer 역할을 한다.
Ingress 는 도메인/경로 라우팅 규칙을 정의하는 Ingress 리소스와 실제로 트래픽을 처리하는 Ingress Controller 로 구성된다. Ingress Controller 의 구현체로는 Nginx, Traefik, HAProxy, Istio Ingress Gateway, ALB 등이 사용된다.
Service vs Ingress
Kubernetes 에서 Pod 는 동적으로 생성되고 사라지기에 IP 역시 자주 바뀐다. 때문에 외부 또는 클러스터 내에서 특정 Pod 에 안정적으로 접근하려면 고정된 네트워크 접근 방법이 필요하다. 이를 해결하기 위해 사용되는 것이 Service 이고 외부 HTTP/HTTPS 트래픽을 세밀하게 제어하기 위해 Ingress 가 사용된다. 즉, Service 는 내부/외부 통신을 연결하는 기본 통로, Ingress 는 HTTP/HTTPS 트래픽을 라우팅하는 고급 제어 도구다. Service 는 4계층(L4, TCP/UDP), Ingress 는 7계층(L7, HTTP/HTTPS)를 담당한다.
| Service | Ingress | |
|---|---|---|
| 목적 | Pod 들을 묶어 네트워크 접근 지점 제공 | HTTP/HTTPS 요청을 경로 기반으로 라우팅 |
| 계층 | L4, TCP/UDP | L7, HTTP/HTTPS |
| 외부 노출 | 필요 시 NodePort, LoadBalancer | 도메인/경로 기반 트래픽 제어 |
| 복잡도 | 단순 연결 | 라우팅, 인증, TLS 등 고급 제어 |
| 연관관계 | Ingress 는 내부적으로 Service 를 사용 | Service 없이는 Ingress 도 작동하지 않음 |
Service 는 여러 Pod 를 하나의 IP 로 접근 가능하게 해주는 기본 네트워크 객체이고, Ingress 는 HTTP 요청을 다양한 경로와 도메인으로 라우팅하는 고급 컨트롤러다. 즉, Ingress 는 Service 를 대체하는 것이 아닌 확장하는 상위 개념이다. Service 가 있어야 Ingress 가 트래픽을 어디로 보낼지 결정할 수 있다. 실무에선 Service + Ingress 조합으로 외부 트래픽을 내부 Pod 로 보낸다.
Ingress Controller
apiVersion: apps/v1
kind: Deployment
metadata:
name: ingress-controller
spec:
replicas: 1
selector:
matchLabels:
name: nginx-ingress
template:
metadata:
labels:
name: nginx-ingress
spec:
serviceAccountName: ingress-serviceaccount
containers:
- name: nginx-ingress-controller
image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.21.0
args:
- /nginx-ingress-controller
- --configmap=$(POD_NAMESPACE)/nginx-configuration
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
ports:
- name: http
containerPort: 80
- name: https
containerPort: 443실제로 트래픽을 처리하는 Ingress Controller 의 역할을 수행할 구현체로 nginx-ingress 를 Deployment 로 배포한다.
apiVersion: v1
kind: Service
metadata:
name: ingress
spec:
type: NodePort
ports:
- port: 80
targetPort: 80
protocol: TCP
name: http
- port: 443
targetPort: 443
protocol: TCP
name: https
selector:
name: nginx-ingressnginx-ingress Deployment Pod 로 외부에 노출하기 위해 NodePort 또는 LoadBalancer 타입의 Service 를 배포한다. 해당 Service 가 외부에서 쿠버네티스 클러스터로 접근하는 단일 진입점이 된다.
Ingress
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress-wear-watch
spec:
rules:
- host: wear.my-online-store.com
http:
paths:
- backend:
serviceName: wear-service
servicePort: 80
- host: watch.my-online-store.com
http:
paths:
- backend:
serviceName: watch-service
servicePort: 80Ingress 가 여기서 사용된다.
Ingress TLS 설정
# openssl 로 tls.crt, tls.key 생성
openssl req -x509 -nodes -days 365 \
-newkey rsa:2048 \
-keyout tls.key \
-out tls.crt \
-subj "/CN=myapp.example.com/0=myapp"
# Secret 으로 생성
k create secret tls my-tls-secret --cert=tls.crt --key=tls.keyapiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: tls-ingress
spec:
ingressClassName: nginx
tls:
- hosts:
- myapp.example.com
secretName: my-tls-secret # Ingress Controller 가 지정된 TLS 인증서 Secret 을 이용해 SSL Handshake 를 수행
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
serviceName: my-service
servicePort: 80Gateway API
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass # 클러스터 전체에서 어떤 Gateway Controller 를 사용할 지 정하는 설계도
metadata:
name: nginx
spec:
controllerName: nginx.org/gateway-controller
--- # TLS/로드밸런서(IP, 인증서)는 운영자가 Gateway 에만 정의
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway # 어느 포트를 열고 어떤 도메인을 받을지 정의
metadata:
name: example-gateway
spec:
gatewayClassName: nginx # 위에 만든 GatewayClass
listeners:
- name: http
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: Same
- name: https
protocol: HTTPS
port: 443
tls:
mode: Terminate
certificateRefs:
- kind: Secret
name: tls-secret
allowedRoutes:
namespaces:
from: All
--- # 개발자는 자기 Route 리소스만 관리 (서비스 라우팅)
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute # 요청을 어느 서비스로 보낼지 정하는 규칙 'Route | HTTPRoute | TLSRoute | GRPCRoute'
metadata:
name: app-route
spec:
parentRefs:
- name: example-gateway
hostnames:
- "www.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /login
backendRefs:
- name: example-svc
port: 8080Ingress 는 헤더 매칭, 리다이렉트, 트래픽 분할 등의 부족한 기능을 보완하기 위해 annotation 을 통해 Ingress Controller 의 설정을 활용한다. Ingress 는 기본적으로 HTTP/HTTPS 정도만 처리하기에 gRPC, TCP, UDP 같은 프로토콜을 지원하기 위해선 Ingress Controller 에 맞는 annotation 을 붙여 사용해야 한다. Ingress Controller 구현체 마다 annotation 이 달라서 Nginx Ingress Controller 를 사용하다가 Traefik Ingress Controller 를 사용하면 annotation 이 달라 설정을 다시 해줘야 한다. 즉, 표준이 아닌 Ingress Controller 별 종속적인 기능에 의존성이 생기는 문제가 있다. 뿐만 아니라 Ingress 는 트래픽 라우팅, TLS, 도메인, 리다이렉트 등 모든 설정이 하나의 Ingress 리소스 안에 섞여버리는 문제도 있다.
쿠버네티스에선 이를 해결하기 위해 Gateway API 라는 프로젝트를 시작했고, 제공되는 GatewayClass (인프라팀), Gateway (운영자), Route (개발자) 등 역할이 분리된 새로운 Object 를 통해 Controller 에 종속되지 않는 설정과 리소스 관리 충돌을 방지할 수 있다. Gateway API 는 쿠버네티스의 기본 리소스가 아니기 때문에 CRD 를 설치해야 한다.
Traefik GatewayClass 설치
helm repo add traefik https://traefik.github.io/charts # helm repo 에 traefik chart 를 추가하고
helm repo update # helm repo update
helm install traefik traefik/traefik \
-n traefik \ # traefik 이라는 namespace 에 설치
--create-namespace \ # namespace 에 생성
--set service.type=LoadBalancer \ # traefik gateway controller 의 Service type
--set providers.kubernetesGateway.enabled=true # traefik 의 경우 Gateway API 를 활성화해줘야 한다.# k get gatewayclass traefik -o yaml
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: traefik
spec:
controllerName: traefik.io/gateway-controller쿠버네티스용 패키지 매니저 Helm 을 사용해서 Traefik GatewayClass 를 설치하자.
Gateway 생성
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: demo-gateway
namespace: default
spec:
gatewayClassName: traefik
listeners:
- name: http
protocol: HTTP
port: 8000 # 포트는 반드시 Traefik Deployment 에 정의된 EntryPoint 포트와 일치
allowedRoutes:
namespaces:
from: All # Gateway 에 들어올 수 있는 namespace 설정 'All | Same | Selector'HTTPRoute 생성
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: echo-route
namespace: default
spec:
parentRefs:
- name: demo-gateway
hostnames:
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: echo-server
port: 80