Skip to content

Instantly share code, notes, and snippets.

@ruo91
Last active June 3, 2021 00:20
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ruo91/db46bc3f8ea0ff78656020f5f611ec90 to your computer and use it in GitHub Desktop.
Save ruo91/db46bc3f8ea0ff78656020f5f611ec90 to your computer and use it in GitHub Desktop.
OpenShift v4.x - Self Upstream DNS 서버구성

OpenShift v4.x - Self Upstream DNS 서버구성

OpenShift에서 제공하는 CoreDNS Operator의 기능적 한계에 대한 자체 구현이 주 목적이며,
CoreDNS Container 이미지를 통해 외부 질의에 대한 것들을 제어 하도록 한다.

이 글을 통해 Production 레벨에서 적용 후 문제가 발생하는 부분에 대해서는 책임을 지지 않는다.

테스트 환경

  • Bastion VM (NAT)
  • RHEL 8.x
  • Podman / SKOPEO / CRI-O

1. Self Upstream DNS가 필요한 이유?

OpenShift를 외부와 단절된 네트워크 환경(disconnected)에서 구성하는 경우
외부 도메인에 대한 질의(Query)가 허용되지 않는다.
이 문제를 해결하기 위해서는 별도 Upstream DNS를 통해 질의가 될 수 있도록 설정해야 한다.

여기서 Upstream DNS라는 것은 외부 도메인에 대한 질의가 가능한 DNS 서버를 의미한다.

2. CoreDNS Operator 기능적 한계란 무엇인가?

OpenShift에서 제공하는 CoreDNS는 Forwarding 기능만을 제공한다.
즉, 별도의 DNS 서버가 존재하고 이를 가져다 사용할 수 밖에 없다는 것이다.

Operator에서는 아래와 같이 Forwarding 기능만 사용 가능하기 때문에, 별도의 DNS Record를 추가할 수 없는 한계가 발생한다.

[root@bastion ~]# oc edit dns.operator/default
apiVersion: operator.openshift.io/v1
kind: DNS
metadata:
  name: default
spec:
  servers:
  - name: test-dns
    zones:
      - test.com
    forwardPlugin:
      upstreams:
        - 192.168.0.2
        - 192.169.0.2:5353
  - name: redhat-dns
    zones:
      - redhat.io
      - redhat.com
    forwardPlugin:
      upstreams:
        - 8.8.8.8
        - 8.8.4.4

- RefURL

[1]: GitHub - CoreDNS Opearator CRD Source

3. 별도의 DNS 서버를 구성하여 사용하면 안되나?

개인이나 소규모 환경에서는 DNS 서버에 관련 도메인을 별도로 만들어 직접 제공해주면 된다.
다만, 이렇게 관리가 되는 경우에는 DNS 서버에 추가 요청사항이 있는 경우 관리 주체가 분명하지 않다.

따라서, 조직이 큰 기업에서는 대부분 단계별(waterfall) 적용 방식으로 일 처리가 진행 되므로
요구사항이 수시로 변경되면 변경에 대한 이유, 작업, 완료까지의 시간이 상당히 많이 지체 된다.

이렇게 잦은 요구사항이 변경되고 시간이 지체되다 보면, 협업 분위기가 상당히 까칠하게 변경될 수 있다.
그래서, Self Upstream DNS Pod를 통해 OpenShift 클러스터 내에서 처리를 하고자 한다.

4. Architecture는 어떻게 되나?

자체 구현하는 Container 이미지는 CoreDNS이며 Daemonset으로 동작하도록 설계 되었다.

4.1. Kubernetes: Pod Scheduling workflow

아래는 기본적인 Kubernetes에서의 Pod 배포에 대한 Scheduling workflow 이다.
(DaemonSet은 Label을 기반으로 모든 노드에 스케줄러가 배포하는 방식이나, 기본적인 Pod 배포 절차와 같다.) Figure 1. Kubernetes: Pod Scheduling workflow

4.2. OpenShift: Self Upstream DNS

Self Upstream DNS는 CoreDNS를 Container 이미지 형태로 만들어 DaemonSet Pod로 배포하여 구성한다.
이 Pod는 인프라 환경에서 최소한의 변경과 격리, 성능, 효율성에 중점을 두고 설계 되었다. Figure 2. OpenShift: Self Upstream DNS

- 격리

  • DNS 서버를 만들 필요가 없다.
  • 방화벽 구성이 필요없다.
  • DNS 질의 요청은 노드의 로컬호스트로 내부에서 처리 된다.

- 효율성

  • DaemonSet 배포 전략으로 노드가 추가/삭제 되어도 Pod가 자동 관리가 된다.
  • 외부 레지스트리로 태깅 되어있는 Container 이미지들에 대해서
    수작업으로 Image Contents Policy를 별도로 추가 작업을 하지 않아도 된다.
  • ConfigMap/DaemonSet 수정을 통하여 도메인 Zone 파일 생성 및 record 종류를 실시간으로 추가/삭제가 가능하다.

- 성능

  • Primary DNS 서버에 없는 요청은 CoreDNS Pod로 내부에서, 처리하기 때문에 외부로 트래픽이 나갈 이유가 없다.
  • CoreDNS 이미지 사이즈는 50Mbyte 이하이며, Pod가 사용하는 CPU 및 Memory 리소스 소비량은 아주 적게 사용 된다.

5. Self Upstream DNS 구성

5.1. 필요 패키지 설치

CoreDNS를 빌드하기 위해서 사용되는 패키지를 설치 한다.

[root@bastion ~]# dnf group install "Development Tools"
[root@bastion ~]# dnf install podman skopeo

5.2. Golang 구성

Golang 1.12+ 버전 이상으로 구성한다.

[root@bastion ~]# curl -LO https://golang.org/dl/go1.16.3.linux-amd64.tar.gz
[root@bastion ~]# tar -xzvf go1.16.3.linux-amd64.tar.gz -C /opt

- Profile 구성

[root@bastion ~]# vi /etc/profile
# Go Language
export GO_DIR=/opt
export GO_HOME=$GO_DIR/go
export GOPATH=$GO_DIR/gopath
export PATH=$PATH:$GO_HOME/bin

5.3. CoreDNS 컴파일

[root@bastion ~]# git clone https://github.com/coredns/coredns.git /opt/coredns/
[root@bastion ~]# cd /opt/coredns
[root@bastion ~]# make

5.4. Dockerfile 생성

Dockerfile에서 scratch 이미지를 사용하여 Layer 없는 구조로 CoreDNS Container 이미지를 만든다.
DNS 서비스 포트는 TCP/53, UDP/53번이 오픈이 된다.

[root@bastion ~]# mkdir /opt/dockerfile
[root@bastion ~]# vi /opt/dockerfile/Dockerfile
FROM scratch
COPY coredns /usr/bin/coredns
EXPOSE 53 53/udp
ENTRYPOINT ["/usr/bin/coredns"]

컴파일 된 CoreDNS Binary 파일을 Dockerfile이 존재하는 위치에 복사 후 podman을 통해 Container 이미지를 빌드한다.

[root@bastion ~]# cp /opt/coredns/coredns /opt/dockerfile/
[root@bastion ~]# cd /opt/dockerfile/
[root@bastion ~]# podman build -t registry.ybkim.ocp4.local/coredns/insecure-registry-only:latest .

5.5. Container 이미지 업로드

생성한 Container 이미지를 Private Registry 서버로 업로드 한다.

[root@bastion ~]# podman push --tls-verify=false registry.ybkim.ocp4.local/coredns/insecure-registry-only:latest

6. DaemonSet YAML 생성

Redhat 및 커뮤니티에 사용되는 외부 레지스트리 주소 태깅을 가진 모든 도메인들에 대해서,
내부에서 사용하는 Private Registry 주소로 CNAME을 설정하였다.

즉, 관련 도메인으로 오는 모든 요청은 내부 Private Registry 주소로 요청 된다.

6.1. ConfigMap 생성

[root@bastion ~]# vi /opt/coredns-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  labels:
    coredns: insecure-registry-only
  name: coredns-config
  namespace: openshift-dns
data:
  Corefile: |
    .:53 {
        errors
        log
        health
        file /opt/quay.io.db quay.io
        file /opt/redhat.io.db redhat.io
        file /opt/redhat.com.db redhat.com
        file /opt/docker.io.db docker.io
        file /opt/gcr.io.db gcr.io
        file /opt/nvcr.io.db nvcr.io
        file /opt/elastic.co.db elastic.co
        cache 30
        reload
    }
  quay.io.db: |
    $ORIGIN	quay.io.
    @	IN      SOA	quay.io.	admin.quay.io.		20210101	21600	3600	604800	86400
    	IN	A	192.168.0.2
    *	IN	CNAME	quay.io.
  redhat.io.db: |
    $ORIGIN redhat.io.
    @	IN	SOA	redhat.io.	admin.redhat.io.	20210101	21600	3600	604800	86400
    	IN	A	192.168.0.2
    *	IN	CNAME	redhat.io.
  docker.io.db: |
    $ORIGIN	docker.io.
    @	IN	SOA	docker.io.	admin.docker.io.	20210101	21600	3600	604800	86400
    	IN	A	192.168.0.2
    *	IN	CNAME	docker.io.
  gcr.io.db: |
    $ORIGIN	gcr.io.
    @	IN	SOA	gcr.io.		admin.gcr.io.		20210101	21600	3600	604800	86400
    	IN	A	192.168.0.2
    *	IN	CNAME	gcr.io.
  nvcr.io.db: |
    $ORIGIN	nvcr.io.
    @	IN	SOA	nvcr.io.	admin.nvcr.io.		20210101	21600	3600	604800	86400
    	IN	A	192.168.0.2
    *	IN	CNAME	nvcr.io.
  elastic.co.db: |
    $ORIGIN	elastic.co.
    @	IN	SOA	elastic.co.	admin.elastic.co.	20210101	21600	3600	604800	86400
    	IN	A	192.168.0.2
    *	IN	CNAME	elastic.co.
  redhat.com.db: |
    $ORIGIN	redhat.com.
    @	IN	SOA	redhat.com.	admin.redhat.com.	20210101	21600	3600	604800	86400
    	IN	A	192.168.0.2
    *	IN	CNAME	redhat.com.

생성된 ConfigMap을 명령어를 통해 생성한다.

[root@bastion ~]# oc create -f /opt/coredns-configmap.yaml

- RefURL

[2]: Custom DNS Entries For Kubernetes

6.2. DaemonSet YAML 생성

앞서 만들었던 ConfigMap을 DaemonSet에 포함하여 해당 Zone 파일 DB가 마운트 되어 사용될 수 있도록 만든다.

[root@bastion ~]# vi /opt/coredns-daemonset.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
  labels:
    coredns: insecure-registry-only
  namespace: openshift-dns
  name: coredns-insecure-registry-only
spec:
  selector:
    matchLabels:
      coredns: insecure-registry-only
  template:
    metadata:
      creationTimestamp: null
      labels:
        coredns: insecure-registry-only
    spec:
      restartPolicy: Always
      nodeSelector:
        kubernetes.io/os: linux
      hostNetwork: true
      containers:
        - name: coredns-insecure-registry-only
          image: registry.ybkim.ocp4.local/coredns/insecure-registry-only:latest
          command:
            - coredns
          args:
            - '-conf'
            - /opt/Corefile
          ports:
          - containerPort: 53
            name: dns
            protocol: UDP
          securityContext:
            privileged: true
            runAsUser: 0
          livenessProbe:
            httpGet:
              path: /health
              port: 8080
              scheme: HTTP
            initialDelaySeconds: 60
            timeoutSeconds: 5
            periodSeconds: 10
            successThreshold: 1
            failureThreshold: 5
          readinessProbe:
            httpGet:
              path: /health
              port: 8080
              scheme: HTTP
            initialDelaySeconds: 10
            timeoutSeconds: 3
            periodSeconds: 3
            successThreshold: 1
            failureThreshold: 3
          volumeMounts:
            - name: coredns-config-volume
              readOnly: true
              mountPath: /opt
      volumes:
        - name: coredns-config-volume
          configMap:
            name: coredns-config
            defaultMode: 420
            items:
              - key: Corefile
                path: Corefile
              - key: quay.io.db
                path: quay.io.db
              - key: redhat.io.db
                path: redhat.io.db
              - key: docker.io.db
                path: docker.io.db
              - key: gcr.io.db
                path: gcr.io.db
              - key: nvcr.io.db
                path: nvcr.io.db
              - key: elastic.co.db
                path: elastic.co.db
              - key: redhat.com.db
                path: redhat.com.db

생성된 Daemonset YAML을 OC CLI를 통해 생성한다.

[root@bastion ~]# oc create -f /opt/coredns-daemonset.yaml

7. OpenShift Image Controller 설정

Image controller에서 Registry Sources에 대한 Insecure Registry 설정을 한다.
Insecure Registry 설정은 SSL 인증서를 사용하지 않고 접근시에 사용 된다.

즉, 목록에 없는 주소는 SSL 인증서를 사용한다고 보면되며, "domain:port"로 구분한다.
또한, 이 작업을 수행시 Worker 노드의 /etc/containers/registries.conf 파일의 내용을 수정하기 때문에,
Machine Config Operator가 이를 반영하기 위해 노드를 재부팅 하므로, 반드시 일정을 잡고 진행하는 것이 좋다.

[root@bastion ~]# oc edit images.config.openshift.io cluster
apiVersion: config.openshift.io/v1
kind: Image
metadata:
  name: cluster
spec:
  registrySources:
    insecureRegistries:
    - quay.io
    - docker.io
    - gcr.io
    - nvcr.io
    - docker.elastic.co
    - registry.redhat.io
    - registry-1.docker.io
    - registry.connect.redhat.com
    - registry2.ybkim.ocp4.local:5000

- RefURL

[3]: OpenShift Docs - Image configuration resources

8. 노드 설정

OpenShift의 Worker 노드를 기준으로 작업을 진행한다.
본 작업은 RedHat CoreOS 기반에서 nmcli 명령어를 통해 수작업으로 진행 한다.

8.1. 네트워크 설정

각 노드별 네트워크 설정시 DNS를 127.0.0.1로 로컬호스트를 등록한다.

- Worker01

[root@bastion ~]# ssh core@worker01.ybkim.ocp4.local
[core@worker01 ~]$ sudo -i
[root@worker01 ~]# nmcli connection mod 'ens3' \
  ipv4.method manual \
  ipv4.addresses 192.168.0.11/24 \
  ipv4.gateway 192.168.0.1 \
  ipv4.dns 192.168.0.3 \
  +ipv4.dns 127.0.0.1 \
  connection.autoconnect yes
[root@worker01 ~]# systemctl restart NetworkManager

- Worker02

[root@bastion ~]# ssh core@worker02.ybkim.ocp4.local
[core@worker02 ~]$ sudo -i
[root@worker02 ~]# nmcli connection mod 'ens3' \
  ipv4.method manual \
  ipv4.addresses 192.168.0.12/24 \
  ipv4.gateway 192.168.0.1 \
  ipv4.dns 192.168.0.3 \
  +ipv4.dns 127.0.0.1 \
  connection.autoconnect yes
[root@worker02 ~]# systemctl restart NetworkManager

9. 테스트

9.1. Container 이미지 업로드

특정 Container 이미지를 받아서 내부 용도로 사용하는 Registry에 업로드 한다.

[root@bastion ~]# skopeo copy --dest-tls-verify=false \
docker://docker.io/library/nginx:stable docker://registry.ybkim.ocp4.local/library/nginx:stable

9.2. Pod 생성

생성시 외부 Registry 이름으로 태깅된 이미지 주소로 선언 후 배포한다.

[root@bastion ~]# vi /opt/nginx-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    web: nginx
  namespace: default
spec:
  containers:
    - name: web
      image: docker.io/library/nginx:stable
      ports:
        - name: web
          containerPort: 80
          protocol: TCP
[root@bastion ~]# oc create -f /opt/nginx-pod.yaml

9.3. Pod 생성 확인

실제 namespace에 Nginx Pod가 해당 외부 도메인으로 태깅 된 이미지로 실행된 것을 확인 가능하다.

[root@bastion ~]# oc describe pod nginx -n default
Events:
  Type    Reason          Age    From               Message
  ----    ------          ----   ----               -------
  Normal  Scheduled       2m27s  default-scheduler  Successfully assigned default/nginx to worker01.ybkim.ocp4.local
  Normal  AddedInterface  2m25s  multus             Add eth0 [172.40.4.18/24]
  Normal  Pulling         2m22s  kubelet            Pulling image "docker.io/library/nginx:stable"
  Normal  Pulled          110s   kubelet            Successfully pulled image "docker.io/library/nginx:stable" in 32.703255413s
  Normal  Created         109s   kubelet            Created container web
  Normal  Started         108s   kubelet            Started container web

9.4. Self Upstream DNS 로그 확인

Scheduler가 worker01 노드에 Pod가 구동 되도록 요청을 하였고,
이후 "docker.io/library/nginx:stable" 이미지를 다운로드 요청 하였다.

Worker01 노드에서 127.0.0.1번의 로컬호스트로 DNS 질의 요청이 진행 된것을 확인해 볼 수 있다.

[root@bastion ~]# oc project openshift-dns
[root@bastion ~]# oc get pod -o wide | grep -v 'dns-default' | grep 'worker01.ybkim.ocp4.local'
coredns-insecure-registry-only-wq52t   1/1     Running   0          4d12h   7.7.7.16      worker01.ybkim.ocp4.local   <none>           <none>

CoreDNS 로그를 확인 해보면 쿼리가 정상적으로 수행된 것을 확인 가능하다.

[root@bastion ~]# oc logs -f coredns-insecure-registry-only-wq52t
[INFO] 127.0.0.1:57289 - 32163 "A IN docker.io. udp 27 false 512" NOERROR qr,aa,rd 52 0.006971549s
[INFO] 127.0.0.1:47821 - 20144 "AAAA IN docker.io. udp 27 false 512" NOERROR qr,aa,rd 96 0.005625527s
[INFO] 127.0.0.1:49749 - 15625 "AAAA IN registry-1.docker.io. udp 38 false 512" NOERROR qr,aa,rd 81 0.000425007s
[INFO] 127.0.0.1:44583 - 5258 "A IN registry-1.docker.io. udp 38 false 512" NOERROR qr,aa,rd 106 0.002122852s
[INFO] 127.0.0.1:35541 - 14083 "AAAA IN registry-1.docker.io. udp 38 false 512" NOERROR qr,aa,rd 81 0.000206334s
[INFO] 127.0.0.1:58193 - 57438 "A IN registry-1.docker.io. udp 38 false 512" NOERROR qr,aa,rd 106 0.000106337s
[INFO] 127.0.0.1:42592 - 41777 "A IN docker.io. udp 27 false 512" NOERROR qr,aa,rd 52 0.00019376s
[INFO] 127.0.0.1:52830 - 56624 "AAAA IN docker.io. udp 27 false 512" NOERROR qr,aa,rd 96 0.000279241s
[INFO] 127.0.0.1:38889 - 34090 "AAAA IN registry-1.docker.io. udp 38 false 512" NOERROR qr,aa,rd 81 0.000256258s
[INFO] 127.0.0.1:47402 - 27894 "A IN registry-1.docker.io. udp 38 false 512" NOERROR qr,aa,rd 106 0.000090793s
[INFO] 127.0.0.1:37396 - 27076 "AAAA IN registry-1.docker.io. udp 38 false 512" NOERROR qr,aa,rd 81 0.00031906s
[INFO] 127.0.0.1:45730 - 6400 "A IN registry-1.docker.io. udp 38 false 512" NOERROR qr,aa,rd 106 0.001418448s
[INFO] 127.0.0.1:33962 - 38558 "AAAA IN registry-1.docker.io. udp 38 false 512" NOERROR qr,aa,rd 81 0.000363738s
[INFO] 127.0.0.1:39435 - 44017 "A IN registry-1.docker.io. udp 38 false 512" NOERROR qr,aa,rd 106 0.000115647s
[INFO] 127.0.0.1:57224 - 44615 "A IN registry-1.docker.io. udp 38 false 512" NOERROR qr,aa,rd 106 0.005924397s
[INFO] 127.0.0.1:34249 - 32450 "AAAA IN registry-1.docker.io. udp 38 false 512" NOERROR qr,aa,rd 81 0.006413477s
[INFO] 127.0.0.1:41334 - 51187 "AAAA IN registry-1.docker.io. udp 38 false 512" NOERROR qr,aa,rd 81 0.00022242s
[INFO] 127.0.0.1:43200 - 46667 "A IN registry-1.docker.io. udp 38 false 512" NOERROR qr,aa,rd 106 0.000349816s

10. 결론

외부와 단절된 네트워크 환경(disconnected)에서는 격리, 성능, 효율성을 생각하면 충분히 시도 해볼만한 구성이라 생각한다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment