본문 바로가기

kubernetes, helm, rancher

kubernetes networks, ingress, ingress controller

1. 기본적인 networking

ip link # interface 확인
ip addr add [본인 cidr ex)192.168.0.5/24] dev [interface name ex)eth0]
route # 다른 network로 가기위한 route table 확인
      # 0.0.0.0의 gateway는 내부망으로 이동.
ip route add [dest cidr 192.168.1.0/24] via [본인 게이트웨이 ex)192.168.0.1]
# dest cidr에 default나 0.0.0.0을 넣어 모든 request는 게이트웨이로 보낼 수 있다.
echo 1 > /proc/sys/net/ipv4/ip_forward # 중간다리 해주는 linux host는 default로 interface 
# forward를 해주지 않는다. 따라서 1로 바꿔서 eth0 -> eth1로 보내게끔 해준다.
# reboot이후에 계속 1로 이용하고 싶다면, /etc/sysctl.conf파일에서 net.ipv4.forward = 1 바꿔준다.

2. DNS

/etc/hosts # local /etc/hosts내의 dns service
[ip] [name]

# 많은 host를 관리할 수 없기 때문에, name server를 지정. 기본적인 nameserver 8.8.8.8
/etc/resolv.conf
nameserver [nameserver ip]
search     [생략할 domain] # web.domain.com말고 web으로 연결하고 싶다면 search에 domain.com
                           # 하면 된다.

# default로 nameserver보다 local /etc/hosts를 먼저본다.
# nameserver를 우선순위로 두고싶다면 /etc/nsswitch.conf를 수정한다.
hosts:       dns files # nameserver 먼저 본다

# domain 정보를 보는 방법 /etc/hosts를 안보고 resolv.conf의 nameserver를 본다. 
nslookup
dig

# dns server만드는 방법
1. coredns 다운로드 및 실행
2. default로 53번 port를 쓰고, /etc/hosts 파일작성 및 Corefile 작성
cat > Corefile
. {
    hosts /etc/hosts
}
3. ./coredns 실행.
  • coreDNS -> service 및 pod용 dns를 지원한다. ip와 name을 매핑.
  • pod에서 모든 컨테이너들은 /etc/resolve.conf 안의 nameserver가 kube-dns인 10.96.0.10의 cluster ip로 되어있다.
  • kubectl get svc -A로 보면 kube-dns라는 service가 있으며, kubectl get pod -A로 보면 coredns가 있다.
  • dns naming 규칙이랑 다른게 coredns에서 service는 [service_name].[namespace].svc.cluster.local로 접근하고, pod는 [pod-ip-address].[namespace].pod.cluster.local로 들어간다.
  • /etc/resolve.conf를 보면 search가 보이는데 생략가능한(예를 들어 default.svc.cluster.local) path를 알려준다.
  • 실제로 kubectl exec -it coredns -- cat /etc/resolv.conf 로 하면 안보이는데 해당 container는 쉘 스크립트 명령어 기능을 지원하지 않기 때문이다. 따라서 보고싶다면, 사이드카를 이용해 보면 된다.
  • pod를 만들 때 coredns를 사용안하고 다른 dns server를 custom하게 지정할 수 있다.
...
kind: Pod
...
spec:
  containers:
  ...
  dnsPolicy: "None"
  dnsConfig:
    nameservers:
      - 1.2.3.4 # name server ip
    searches:
      - default.svc.local
    options:
      - name: ndots
        value: "2"
        name: edns0
        
 # dns를 custom하게 만들 수 있다.

3. network namespaces

# network namespace는 container 기반에서 쓰인다.
# 각각의 network namespace는 가상의 독립된 network를 가지며 각각 route table을 가진다.

ip netns add red  # network namespace 만들기
ip netns add blue
ip netns # network namespace list
ip netns exec [network name] ip link # == ip -n [network name] link
ip link add [veth-red] type veth peer name [veth-blue]
ip link set [veth-red] netns [red]   # ip link하면 interface veth-red, veth-blue가 보이는데,
ip link set [veth-blue] netns [blue] # ns에 set하면 host ns의 interface가 아니니까 ip link에
                                     # 보이지 않게 된다.
ip -n [red] addr add [ip] dev [veth-red]
ip -n [blue] addr add [ip] dev [veth-blue]
ip -n [red] link set [veth-red] up
ip -n [blue] link set [veth-blue] up
# 만약 특정 namespace interface를 보고 싶다면, ip netns exec [ns name] ip addr.

# 여기까지 각각의 namespace를 만들고 interface와 ip를 부여해 virtual pipe를 연결했다.
# 이제 bridge를 연결해 외부와 통신하도록 한다.
# bridge를 통해 통신하니 위에서 만든 virtual pipe는 제거하도록한다.

ip -n [red] link del [veth-red] # 한 곳의 interface만 del해도 연결된 interface도 자동으로 제거.
                                # ip netns exec [ns name] ip addr 해보면 lo밖에 없다.
ip link add [veth-bridge] type bridge
ip link set dev [veth-bridge] up
ip link add [veth-red] type veth peer name [veth-bridge-red] # bridge와 연결할 pipe 생성.
ip link add [veth-blue] type veth peer name [veth-bridge-blue]
ip link set [veth-red] netns [red]
ip link set [veth-bridge-red] master [veth-bridge]
ip link set [veth-blue] netns [blue]
ip link set [veth-bridge-blue] master [veth-bridge]
ip -n [red] addr add [ip] dev [veth-red]
ip -n [blue] addr add [ip] dev [veth-blue]
ip -n [red] link set [veth-red] up
ip -n [blue] link set [veth-blue] up
ip addr add [bridge cidr] dev [veth-bridge] # local host로부터 ns로 연결하기 위해 ip 부여

# 하지만 여기까지는 같은 네트워크 내에서 bridge로 연결된 것일 뿐이다.
# 따라서 외부 다른 네트워크로 연결하기 위해서는 route table 설정

ip netns exec [blue] ip route add [another network] via [gateway]
iptables -t nat -A POSTROUTING -s [source cidr] -j MASQUERADE # 외부 네트워크가 내부로
# 들어오기 위해 IP 변환.

4. Docker network

docker는 default로 bridge를 생성
docker network ls 로 확인가능하며 name은 bridge이다
host에서는 docker0라고 저장되는데 이는 ip link로 확인가능.
그리고 bridge와 container 내부의 network끼리 연결하는 것은 위에서 다뤘던 것처럼
각각 interface와 ip를 부여하고 연결하는 것이다.
1. Network namespace 생성
2. network interface 생성
3. virture pipe 생성
4. interface를 각각 namespace, bridge에 연결
5. ip 할당
6. interface up
7. NAT - IP Masquerade 
port forwarding은 iptables -t nat -A DOCKER -j DNAT --dport 8080 --to-destination 80
으로 한다.

5. CNI(Container Network Interface)

  • CNI는 위의 network 생성과정들을 해주는 addon 프로그램이다.
  • kubelet이 container를 관리해준는데, 그때 --cni-conf-dir=/etc/cni/net.d(설정파일모음) --cni-bin-dir=/opt/cni/bin(특정 cni 실행파일모음) 을 지정하여 networking을 해준다. 위치는 /var/lib/kubelet/config.yaml 에서 고쳐주며, systemctl restart kubelet해주면 된다.
  • /etc/cni/net.d/weavnet.conf 안에 version, type, ipam 등 여러 설정들이 존재한다.
  • cni는 node, pod가 많아질수록 route table의 과부화와 성능이 안좋아지기때문에 networking만을 실행하는 프로세스이다.
  • kube proxy와 cni의 차이점은 kube-proxy는 service단에서 여러 pod들을 node에 상관없이 routing 해주는 것이며, kube-apiserver --service-cluster-ip-range [ipNet]으로 service ip range를 설정해줄 수 있다. routing방법으로 kube-proxy --proxy-mode [userspace | iptabltkes | ipvs ..]으로 설정해줄 수 있다. 기본 iptables. ps aux | grep kube-api-server 로 확인 가능.
  • kube-apiserver는 /etc/kubernetes/manifest/kube-apiserver.yaml에서 수정할 수 있고, kube-proxy는 kube-system namespace안에 kube-proxy configmaps가 있으므로, edit으로 vi로 들어가서 mode를 수정하면 된다.
  • 예를 들어 cluster ip를 통해 3개의 노드를 연결했다면 kubelet은 kube-proxy에게 iptables를 만들어 특정 cluster ip로 들어오면 3개 노드의 정보를 만들고 도착지로 설정한다. -> iptables -t nat -S | grep [cluster-ip] 로 확인할 수 있다.
  • kube-proxy는 config file이 docker container 내부에 있다.
  • cni는 여러 pod들과 node들의 ip를 중복되지 않게 부여하며, 관리하고, routing을 하게 해주는 전반적인 networking 프로세스이다.
  • kubectl logs <weave-pod-name> weave(container name) -n kube-system | grep ipalloc-range -> cni log를 통해 pod 할당 ip를 알 수 있다.
  • cat /etc/kubernetes/manifests/kube-apiserver.yaml | grep cluster-ip-range -> service ip range를 알 수 있다.
  • kubectl logs -n kube-system [kube proxy pod name] | grep mode -> mode를 알 수 있다.(ex: iptables)
  • weave net을 설치한다.
kubectl apply -f "https://cloud.weave.works/k8s/net?k8s-version=$(kubectl version | base64 | tr -d '\n')"​
  • CNI는 daemonset으로 작동하며, 각각의 노드에 pod를 생성하여, ip route 역할을 해준다.
  • kubectl get daemonsets.apps -A 를하면 kube-proxy와 weave-net이 node 개수별로 있는 것을 확인할 수 있다.
  • 외부에서 특정 url을 통해 들어오는 request를 load-balancer가 받고 30000번 이상의 node port로 보내주는 과정에서 여러 path가 존재할 때 자원 소모가 크고, ssl 등과 같은 config 설정 및 유지 보수가 힘들기 때문에, node port로 url이 들어오면 나머지 과정을 ingress가 알아서 해준다.
  • ingress controller -> 외부에서 요청이 들어오면 받아서 service로 routing하고, 이것 뿐만 아니라 다양한 ingress controller가 있다. 여러 웹 관련 pod들을 묶어놓은 sevice들을 묶어서 처리. ex) path마다 service가 있을 때 path operation을 처리해준다. (ex: AWS, GCE, nginx ingress)
  • ingress controller는 cvp, http, load balancer, nginx, istio 등 여러가지가 있으며 default로 설치되지 않으므로 설치해야한다.
  • nginx ingress controller를 위한 deploy.yaml(node port)을 다운로드 받으면 ingressClass, ingressdeployment, service, serviceaccount 등의 여러가지 resource kind가 있다.
  • 하나하나 설치하자면, 먼저 nginx controller를 설치해보자.
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-ingress-controller
spec:
  replicas: 1
  selector:
    matchLabels:
      name: nginx-ingress
  template:
    metadata:
      labels:
        name: nginx-ingress
    spec:
      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 # nginx-configuration name을 가진
                                                         # configMap을 만들어, timeout, ssl 등 
                                                         # 설정을 용이하게 만들기 위함.
        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를 외부 nodeport로 연결하기 위해서 service를 만들어 준다.
apiVersion: v1
kind: Service
metadata:
  name: nginx-ingress
spec:
  type: NodePort
  ports:
  - port: 80
    targetPort: 80
    protocol: TCP
    name: http
  - port: 443
    targetPort: 443
    protocol: TCP
    name: https
  selector:
    name: nginx-ingress
  • 그런 다음, ServiceAccount를 만들어 rolebinding을 통해 ingress controller의 permission을 정해준다. (ingress resource를 관리하고 다른 resource에 대한 접근을 제한하기 위해)
apiVersion: v1
kind: ServiceAccount
metadata:
  name: nginx-ingress-serviceaccount
  • controller를 설치했으니 backend 규칙을 정하기 위해 ingress를 만들어 보자.
  • Kubernetes Ingress -> HTTP나 HTTPS를 통해 클러스터 내부의 서비스를 외부로 노출.
  • Service에 외부 URL을 제공, 트래픽을 load balancing, ssl 인증서 처리, virtual hosting 지정.
apiVersion: networking.k8s.io/v1 # v1.22+ 에서 
kind: Ingress
metadata:
  name: ingress-wear
spec:
  rules:
  - http:
    paths:
    - path: /wear
      pathType: Prefix
      backend:
        service:
          name: wear-service
          port:
            number: 80
    - path: /watch
      pathType: Prefix
      backend:
        service:
          name: watch-service
          port:
            number: 80
  - host: videos.domain.com
    http:
    ...
 # yaml을 apply하면 ingress controller에게 전달되어 실행된다.
  • kubectl create ingress [ingress-name] --rule='wear.domain.com/path*=service name:port'로 imperative하게 만들 수 있다.
  • kubectl describe ingress [ingress name]를 하면 default-http-backend:80가 보이는데 이건 어느 경로도 찾을 수 없을때 들어가는 곳이다.
  • 정리해서 말하자면, ingress service가 떠있고 80,443포트로 들어오면 그걸 nginx controller를 실행한 파드로 보낸다. nginx controller는 각각의 namespace의 ingress를 관리한다. application pod가 있는 namespace에 ingress도 떠있고, 그안의 설정된 경로에 따라서 각 파드로 보낸다.
  • network policy를 지정하여 ingress, egress 규칙을 정할 수 있다.
  • weave-net, romana, calico, kube-router는 netwokr policy를 제공해주지만, flannel은 그렇지 않다.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: [name]
spec:
  podSelector:
    matchLabels:
      role: db
  policyTypes:
  - Ingress
  - egress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          name: api-pod
      namespaceSelector:
        matchLabels:
          name: prod
    # 위에서는 podSelector와 namespaceSelector가 and로 작동하지만, - namespaceSelector를 통해
    # 따로 정의해주면 or로 한다.
    - ipBlock: # 특정 cidr을 접근허용해준다.
        cidr: 192.168.5.10/32
    ports:
    - protocol: TCP
      port: 3306
  egress:
  - to:
    - ipBlock:
        cidr: 192.168.5.10/32
    ports:
    - protocol: TCP
      port: 80