'Technical'에 해당되는 글 126건

GCP, AWS와 같은 Public cloud나 On-premise 에서도, 외부에 secure web 서비스를 제공하기 위해 http entrypoint에 대해
유효한 SSL 인증키를 적용하기 위해서는 많은 번거로운 작업 과정을 거쳐야 한다. 그 방법도 여러 가지가 존재하겠지만 여기서는
Kubernetes의 장점을 살려, 간단한 설정과 수작업이 최소화 된 간편 버전의 솔루션을 직접 구현해 보고 정리한 문서를, 따라하기 버전으로 소개하려 한다.

Traefik ingress 의 entrypoint에 대해 Let's Encrypt 로부터 유효(정식) 인증키를 적용해 주는 cert-manager(Jetstack 버전)
를 활용한 방법을 아래에 정리해 둔다.

 


Cert-manager 활용 - jetstack/cert-manager with Traefik v2

  • 사전 요건(Pre-requisites)
    • 사용 가능한 FQDN(여기서는 route53.kube.click을 사용)과 외부 접속 가능한 공인(Public) IP
    • CRD(Custom Resource Definition) 적용 가능한 Kubernetes 환경(여기서는 Local, on-premise kubernetes cluster)
    • Kubernetes 1.15 버전 이상 전제(그 이전 버전은 아래 Reference 4 번 째 내용 참조) 

1. Install Traefik v2 & set-up DNS hosted zone

Traefik ingress 를 제공하는 Helm chart는 stable/traefik(v1)과 traefik/traefik(v2) 가 대표적이다. 여기서는 그 중 traefik/traefik 차트를 사용할 것이다. 상대적으로 Nginx ingress controller나 stable/traefik 보다는 통합적이고 단순하면서도 미래 지향적인 아키텍처와 향상된 기능, 다양한 provider들(Docker, Kubernetes, Rancher, Marathon, AWS ECS 등)을 지원하는 장점을 보인다는 정도로 이해하자.

최근 출시된 것이라 당연한 말이겠지만, 앞 선 다른 Ingress controller 들에 비해 가장 최근에 선보인 것으로 2019년 9월에 GA(Generally Available)되었고, Containous Traefiklabs에서 traefik proxy라는 이름으로 정식 출시되었다.

 

Traefik v2를 위한 다양한 옵션들이 존재하는데, 자세한 사항은 github.com/traefik/traefik/ 을 참고하도록 하고 다음과 같이 traefik/values.yaml 파일을 하나 작성 해 둔다.

additionalArguments:
  - "--accesslog=true"
  - "--accesslog.format=json"
  - "--log.level=INFO"
  - "--entrypoints.websecure.http.tls"
  - "--providers.kubernetesIngress.ingressClass=traefik-cert-manager"
  - "--ping"
  - "--metrics.prometheus"
  - "--api.dashboard=true"
  - "--providers.kubernetescrd"
  - "--providers.kubernetesingress"

deployment:
  replicas: 2

 

Helm v3를 사용해서 traefik 네임스페이스에 Containous helm chart를 통해 traefik을 설치한다. 단, 이 방식으로 설치하면 Traefik 서비스의 Type이 LoadBalancer 방식으로 결정되며, 이 서비스에 외부에서 접속 가능한 공인 IP가 자동 할당된다.

$ helm repo add traefik https://containous.github.io/traefik-helm-chart
$ helm repo update

$ kubectl create namespace traefik
$ helm install --namespace traefik traefik traefik/traefik --values traefik/values.yaml

 

다음 과정에서 A record의 IP 주소로 등록하기 위해 Traefik 서비스의 External IP를 확인한다(여기서는 보안상 실제 IP를 redact 하여 20X.XXX.XXX.XX5 로 표현한다).

$ kubectl get service -n traefik
----------
NAME      TYPE           CLUSTER-IP      EXTERNAL-IP      PORT(S)                      AGE
traefik   LoadBalancer   172.20.56.175   20X.XXX.XXX.XX5   80:32522/TCP,443:32428/TCP   3d7h

 

본 과정에서 사용할 DNS(Route53) 설정으로 가서 route53.kube.click 서브도메인 설정을 다음 캡처 화면과 같이 Wildcard DNS 방식으로 설정하여 준비해 둔다. AWS, GCP 등에서 서브도메인 설정 방법이 궁금하다면 지난 포스팅 bryan.wiki/305 를 참고해보자.

Route53 서브도메인의 호스팅영역 세부정보

 

DNS의 설정이 끝났으면 host 명령을 통해 외부 접속이 가능한지 확인한다.

$ host route53.kube.click
----------
route53.kube.click has address 2XX.XXX.XXX.XX5

$ host svc.route53.kube.click
----------
svc.route53.kube.click has address 2XX.XXX.XXX.XX5

2. Access to the traefik dashboard

후반으로 가면 Dashboard 접근을 위해 Ingressroute를 생성하고 basic_auth로 로그인 가능한 작업이 진행될 테지만 여기서는 port-forward로 traefik pod의 dashboard UI로 직접 접근이 가능한지 우선 확인해 보자.

Tcp port 9000은 traefik pod의 dashboard UI에 연결되도록 설정된 것이다
# Choose only 1 pod, 'cause we deployed it with replicas=2
$ kubectl get pods -n traefik --selector "app.kubernetes.io/name=traefik" --output=name
----------
pod/traefik-68f54c4b94-cjrrl
pod/traefik-68f54c4b94-w8tnr

$ kubectl port-forward -n traefik pod/traefik-68f54c4b94-cjrrl 9001:9000

이제 웹브라우저를 통해 http://127.0.0.1:9001/dashboard/ 로 접속이 가능할 것이다. 

 

참고로 위의 port 9000은, traefik pod의 dashboard UI에 연결되도록 설정된 것으로, 해당 포트를 알아 내려면 'kubectl get pod -n traefik traefik-68f54c4b94-cjrrl -o json' 와 같이 Pod 구성 전체를 보거나, 또는 아래와 같이 해당 port 를 jsonpath 조회를 통해 직접 찾아낼 수 있다. 
kubectl get pod -n traefik traefik-68f54c4b94-cjrrl -o \
  jsonpath='{.spec.containers[0].ports[?(@.name=="traefik")].containerPort}'

3. Install demo whoami application

whoami 네임스페이스를 만들고 그 내부에 whoami app 리소스를 create(apply)한다.

SSL verify 가 아직 되지 않았기 때문에, curl을 통해 접속해 볼 때는 '--insecure' 또는 '-k' 옵션을 사용해야 한다.
$ kubectl create namespace whoami
$ cat << EOF > whoami-svc-ingress.yaml
kind: Deployment
apiVersion: apps/v1
metadata:
  name: whoami
  namespace: whoami
spec:
  replicas: 1
  selector:
    matchLabels:
      app: whoami
  template:
    metadata:
      labels:
        app: whoami
    spec:
      containers:
        - name: whoami
          image: containous/whoami
          imagePullPolicy: Always
---
apiVersion: v1
kind: Service
metadata:
  name: whoami
  namespace: whoami
  labels:
    app: whoami
spec:
  type: ClusterIP
  ports:
    - port: 80
      name: whoami
  selector:
    app: whoami
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: whoami
  namespace: whoami
spec:
  entryPoints:
    - websecure
  routes:
    - match: Host(`whoami.route53.kube.click`)
      kind: Rule
      services:
        - name: whoami
          port: 80
  tls:
    secretName: whoami-cert-tls
EOF

$ kubectl apply -f whoami-svc-ingress.yaml
$ curl http://whoami.route53.kube.click/
----------
404 page not found

$ curl https://whoami.route53.kube.click/
----------
curl: (60) SSL certificate problem: unable to get local issuer certificate
More details here: https://curl.haxx.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.

$ curl --insecure https://whoami.route53.kube.click/
----------
Hostname: whoami-5c8d94f78-xqzvp
IP: 127.0.0.1
IP: 172.21.195.33
RemoteAddr: 172.21.195.9:57340
GET / HTTP/1.1
Host: whoami.route53.kube.click
User-Agent: curl/7.64.1
Accept: */*
Accept-Encoding: gzip
X-Forwarded-For: 2XX.XXX.XXX.XX3
X-Forwarded-Host: whoami.route53.kube.click
X-Forwarded-Port: 443
X-Forwarded-Proto: https
X-Forwarded-Server: traefik-64b966b4b7-8xh28
X-Real-Ip: 2XX.XXX.XXX.XX3

4. Install cert-manager

# Install the CustomResourceDefinition resources separately
$ kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.0.2/cert-manager.crds.yaml

# Create the namespace for cert-manager
$ kubectl create namespace cert-manager

# Add the Jetstack Helm repository
$ helm repo add jetstack https://charts.jetstack.io

# Update your local Helm chart repository cache
$ helm repo update

# Install the cert-manager Helm chart
$ helm install \
    cert-manager jetstack/cert-manager \
    --namespace cert-manager \
    --version v1.0.2

# Verify installation
$ kubectl get pods --namespace cert-manager
----------
NAME                                       READY   STATUS    RESTARTS   AGE
cert-manager-7cdc47446d-z99s6              1/1     Running   0          3d10h
cert-manager-cainjector-6754f97f69-58qsr   1/1     Running   0          3d10h
cert-manager-webhook-7b56df6ddb-zkpx2      1/1     Running   0          3d10h

5. Create cluster issuer + certificate for whoami.route53.kube.click

약간의 시간이 지난 후, ACME solver Pod(cm-acme-http-solver-xxxxx)가 생성되어 cert-manager를 통해
Let's Encrypt 로부터 certificate를 발급받는 과정이 자동으로 진행된다(Log 마지막의 Error 는 Pod가 종료되면서
나타나는 정상적인 상황이므로 무시해도 된다
$ cat << EOF > whoami-cluster-issuer.yaml
apiVersion: cert-manager.io/v1alpha2
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging-issuer-whoami
spec:
  acme:
    email: my@email.id
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      # Secret resource used to store the account's private key.
      name: letsencrypt-staging-whoami
    solvers:
      - selector:
          dnsNames:
            - whoami.route53.kube.click
        http01:
          ingress:
            class: traefik-cert-manager
---
apiVersion: cert-manager.io/v1alpha2
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod-issuer-whoami
spec:
  acme:
    email: my@email.id
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      # Secret resource used to store the account's private key.
      name: letsencrypt-prod-whoami
    solvers:
      - selector:
          dnsNames:
            - whoami.route53.kube.click
        http01:
          ingress:
            class: traefik-cert-manager
EOF

cat << EOF > whoami-certificate.yaml
---
apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
  name: whoami-cert
  namespace: whoami
  annotations:
    cert-manager.io/issue-temporary-certificate: "true"
spec:
  commonName: whoami.route53.kube.click
  secretName: whoami-cert-tls
  dnsNames:
    - whoami.route53.kube.click
  issuerRef:
    name: letsencrypt-prod-issuer-whoami
    kind: ClusterIssuer
EOF

$ kubectl apply -f whoami-cluster-issuer.yaml
$ kubectl apply -f whoami-certificate.yaml
$ kubectl logs -n whoami cm-acme-http-solver-xxxxx
----------
...
I0928 16:20:08.132986       1 solver.go:72] cert-manager/acmesolver "msg"="comparing host" "base_path"="/.well-known/acme-challenge"  "host"="whoami.route53.kube.click" "path"="/.well-known/acme-challenge/ZzsXXXdw" "token"="ZzsXXXdw" "expected_host"="whoami.route53.kube.click"

I0928 16:20:08.133026       1 solver.go:79] cert-manager/acmesolver "msg"="comparing token" "base_path"="/.
well-known/acme-challenge" "host"="whoami.route53.kube.click" "path"="/.well-known/acme-challenge/ZzsXXXdw" "token"="ZzsXXXdw" "expected_token"="ZzsXXXdw"

I0928 16:20:08.133049       1 solver.go:87] cert-manager/acmesolver "msg"="got successful challenge request, writing key" "base_path"="/.well-known/acme-challenge" "host"="whoami.route53.kube.click" "path"="/.well-known/acme-challenge/ZzsXXXdw" "token"="ZzsXXXdw"

Error: http: Server closed
Usage:
  acmesolver [flags]
Flags:
      --domain string     the domain name to verify
  -h, --help              help for acmesolver
      --key string        the challenge key to respond with
      --listen-port int   the port number to listen on for connections (default 8089)
      --token string      the challenge token to verify against
http: Server closed

 

whoami 네임스페이스 내에 cert-manager 를 통해 생성되는 주요 resource 들이 정상 상태로 관찰되는지 확인한다.
$ kubectl describe certificate -n whoami whoami-cert
$ kubectl describe certificaterequest -n whoami whoami-cert-abcde
$ kubectl describe order -n whoami whoami-cert-abcde-1234567890

 

SSL 인증키가 Let's Encrypt 로부터 정상 발급되어 조회되는지 확인한다. 정상 발급이 되지 않았거나 위의 발급 요청 과정을 수행하기 전에는 'Issuer: CN=TRAEFIK DEFAULT CERT' 와 같은 결과가 나올 것이다.
$ echo | openssl s_client -showcerts -servername whoami.route53.kube.click -connect whoami.route53.kube.click:443 2> /dev/null | openssl x509 -inform pem -text | grep 'Issuer'
----------
        Issuer: C=US, O=Let's Encrypt, CN=Let's Encrypt Authority X3
                CA Issuers - URI:http://cert.int-x3.letsencrypt.org/

 

'--insecure' 옵션 없이 https 접속이 가능한지 확인한다. 웹 브라우저로도 확인해 보자. 웹주소 왼 쪽에 자물쇠 모양이 나온다면 정상.
$ curl https://whoami.route53.kube.click/
----------
Hostname: whoami-5c8d94f78-44v6m  
IP: 127.0.0.1  
IP: 172.21.195.128  
RemoteAddr: 172.21.195.137:43042  
GET / HTTP/1.1  
Host: whoami.route53.kube.click  
User-Agent: curl/7.64.1  
Accept: _/_  
Accept-Encoding: gzip  
X-Forwarded-For: 2XX.XXX.XXX.XX3  
X-Forwarded-Host: whoami.route53.kube.click  
X-Forwarded-Port: 443  
X-Forwarded-Proto: https  
X-Forwarded-Server: traefik-55c75f88-p97qd  
X-Real-Ip: 2XX.XXX.XXX.XX3

6. Cleanup

$ kubectl delete -f whoami-cluster-issuer.yaml
$ kubectl delete -f whoami-svc-ingress.yaml
$ helm uninstall cert-manager --namespace cert-manager
$ kubectl delete -f https://github.com/jetstack/cert-manager/releases/download/v1.0.2/cert-manager.crds.yaml
$ helm uninstall --namespace traefik traefik
$ kubectl delete namespace traefik
$ kubectl delete namespace cert-manager

References:

doc.traefik.io/traefik/

 

Traefik

 Welcome Traefik is an open-source Edge Router that makes publishing your services a fun and easy experience. It receives requests on behalf of your system and finds out which components are responsible for handling them. What sets Traefik apart, beside

doc.traefik.io

community.traefik.io/t/traefik-v2-helm-a-tour-of-the-traefik-2-helm-chart/6126 

 

Traefik v2 & Helm - A tour of the Traefik 2 Helm Chart

HemChart & Traefik Hello everyone, and welcome to our quick tour of the Traefik 2 Helm Chart, my favorite way of installing Traefik on Kubernetes. Today, we'll walk you through common scenarios to get you started. By the end of the article, you'll have a f

community.traefik.io

medium.com/dev-genius/quickstart-with-traefik-v2-on-kubernetes-e6dff0d65216

 

Quickstart with Traefik v2 on Kubernetes

5-minute setup of Traefik, Let’s Encrypt, and Cloudflare

medium.com

cert-manager.io/docs/installation/kubernetes/

 

Kubernetes

cert-manager runs within your Kubernetes cluster as a series of deployment resources. It utilizes CustomResourceDefinitions to configure Certificate Authorities and request certificates. It is deployed using regular YAML manifests, like any other applicati

cert-manager.io

 

- Barracuda -

블로그 이미지

Barracuda

Bryan의 Tech-Log. 기록은 역사다. 나는 역사를 공유하고 그 안에서 배우며, 또 다른 역사를 써나간다

댓글을 달아 주세요

 

3. Google cloud DNS managed zone과 External DNS로 kubernetes service 연결하기


지난 2개의 글(https://bryan.wiki/305, https://bryan.wiki/306) 에 이어서, 이번에는 Google cloud DNS에 hosted-zone을 생성하여 Local kubernetes 의 external-dns를 통해 deploy 되는 service를 연결해 보자. 시리즈 처음 글에서 Google cloud DNS 측에 생성한 gcloud.kube.click 도메인을 사용한다.

 


 

 


Google cloud DNS project의 managed zone 확인

GCP console 좌측 메뉴에서 Network services > Cloud DNS > Zone details 을 통해서도 확인할 수 있다

Google cloud DNS의 managed zone은 프로젝트에 귀속되는 리소스이다

$ export PROJECT_NAME="dns-hosting-poc"
$ gcloud config set project $PROJECT_NAME

$ export CUSTOM_DOMAIN="gcloud.kube.click"
$ export DNS_ZONE_NAME="subdomain-route53"
$ export IAM_ACCOUNT="gcloud-dns-admin"

$ gcloud dns record-sets list --zone=$DNS_ZONE_NAME
----------
NAME                TYPE  TTL    DATA
gcloud.kube.click.  NS    21600  ns-cloud-e1.googledomains.com.,ns-cloud-e2.googledomains.com.,ns-cloud-e3.googledomains.com.,ns-cloud-e4.googledomains.com.
gcloud.kube.click.  SOA   21600  ns-cloud-e1.googledomains.com. cloud-dns-hostmaster.google.com. 1 21600 3600 259200 300

 

  • IAM service-account 생성, roles/dns.admin 과 바인딩하고 credential key 다운로드
$ gcloud --project $PROJECT_NAME iam service-accounts create $IAM_ACCOUNT --display-name "Service Account to support external-dns"

$ gcloud projects add-iam-policy-binding $PROJECT_NAME --member serviceAccount:$IAM_ACCOUNT@$PROJECT_NAME.iam.gserviceaccount.com --role roles/dns.admin

$ gcloud --project $PROJECT_NAME iam service-accounts list
----------
DISPLAY NAME                             EMAIL                                                     DISABLED
Service Account to support external-dns  gcloud-dns-admin@dns-hosting-poc.iam.gserviceaccount.com  False

$ gcloud iam service-accounts keys create key.json --iam-account=$IAM_ACCOUNT@$PROJECT_NAME.iam.gserviceaccount.com

 

다운로드 된 credential 로부터 Kubernetes secret 등록한다. 이 credential을 사용해서 external-dns가 Google cloud DNS에 접근하게 된다

Bitnami 버전의 external-dns 컨테이너는 Google cloud DNS 접근을 위한 인증 키 값을 credentials.json 에서 읽어 들인다

$ kubectl create secret generic gcloud-dns-key --from-file=credentials.json=./key.json

 


Kubernetes external-dns deploy

Helm을 통해 external-dns 를 deploy 한다.

txtOwnerId 로는 본 프로젝트의 목적과 연결되는 것이 확인 되도록 적당히 gcloudpoc를 사용한다

$ helm install external-dns bitnami/external-dns \
  --set provider=google \
  --set google.project=$PROJECT_NAME \
  --set google.serviceAccountSecret=gcloud-dns-key \
  --set txtOwnerId=gcloudpoc \
  --set domainFilters\[0\]=$CUSTOM_DOMAIN \
  --set policy=sync

$ kubectl logs external-dns-bfbcb5dc9-7djnz
----------
...
time="2020-09-13T16:04:08Z" level=info msg="Instantiating new Kubernetes client"
time="2020-09-13T16:04:08Z" level=info msg="Using inCluster-config based on serviceaccount-token"
time="2020-09-13T16:04:08Z" level=info msg="Created Kubernetes client https://10.100.0.1:443"
time="2020-09-13T16:04:17Z" level=info msg="All records are already up to date"
time="2020-09-13T16:05:19Z" level=info msg="All records are already up to date"

 

 


접속 대상인 Kubernetes service deploy

Nginx 로 이루어진 kubernetes service 를 다음과 같이 정의하자.

Type을 LoadBalancer 로 하고 svc1 서비스에 대해 Kubernetes cluster 에서 사용 가능한 주소 대역이 할당되도록 설정하면 되는데, 여기서 보이는 10.0.0.* 대역의 IP가 지금은 사설 IP 대역이지만 실제 운영시에는 공인 IP 대역으로, 외부 접근 가능한 주소로 보면 된다(annotation의 도메인 부분을 제외하면 Route53 경우와 거의 유사함)

service-example.yaml

apiVersion: v1
kind: Service
metadata:
  name: svc1
  annotations:
    external-dns.alpha.kubernetes.io/hostname: svc1.gcloud.kube.click
  labels:
    app: nginx
spec:
  type: LoadBalancer
  ports:
  - port: 80
    name: http
    targetPort: 80
  selector:
    app: nginx

---

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx
        name: nginx
        ports:
        - containerPort: 80
          name: http
$ kubectl apply -f service-example.yaml
----------
service/svc1 created
deployment.apps/nginx created

$ kubectl get service -l app=nginx
----------
NAME   TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
svc1   LoadBalancer   10.100.89.153   10.0.0.201    80:32221/TCP   1m

 

약간의 시간이 흐른 후 External-dns 의 log 를 살펴 보면 다음과 같은 내용과 함께, Route53 에 새로운 A 레코드와 TXT 레코드가 추가되 어 있는 것을 볼 수 있다.

 


Curl 또는 웹 브라우저로 도메인 접속

$ curl http://10.0.0.201
----------
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

 

 


Kubernetes service 삭제

 

External-dns deploy 옵션에서 policy=sync 로 설정했기 때문에, 접속 대상이 되는 service를 삭제하면 Google cloud DNS의 gcloud.kube.click 호스팅 영역의 svc1.gcloud.kube.click 레코드도 자동으로 삭제된다

$ kubectl delete -f service-example.yaml
----------
service/svc1 deleted
deployment.apps/nginx deleted

 

약간의 시간이 흐른 후 External-dns 의 log 를 살펴 보면 다음과 같은 내용과 함께, Cloud DNS의 svc1에 대한 A 레코드와 TXT 레코드가 삭제되어 있는 것을 볼 수 있다.

$ kubectl logs external-dns-bfbcb5dc9-7djnz
----------
...
time="2020-09-20T16:31:11Z" level=info msg="All records are already up to date"
time="2020-09-20T16:32:11Z" level=info msg="All records are already up to date"
time="2020-09-20T16:33:13Z" level=info msg="Change zone: subdomain-route53 batch #0"
time="2020-09-20T16:33:13Z" level=info msg="Del records: svc1.gcloud.kube.click. A [10.0.0.201] 10"
time="2020-09-20T16:33:13Z" level=info msg="Del records: svc1.gcloud.kube.click. TXT [\"heritage=external-dns,external-dns/owner=gcloudpoc,external-dns/resource=service/default/svc1\"] 300"
...

$ gcloud dns record-sets list --zone=$DNS_ZONE_NAME
----------
NAME                TYPE  TTL    DATA
gcloud.kube.click.  NS    21600  ns-cloud-e1.googledomains.com.,ns-cloud-e2.googledomains.com.,ns-cloud-e3.googledomains.com.,ns-cloud-e4.googledomains.com.
gcloud.kube.click.  SOA   21600  ns-cloud-e1.googledomains.com. cloud-dns-hostmaster.google.com. 1 21600 3600 259200 300

 

Barracuda

 

블로그 이미지

Barracuda

Bryan의 Tech-Log. 기록은 역사다. 나는 역사를 공유하고 그 안에서 배우며, 또 다른 역사를 써나간다

댓글을 달아 주세요

 

2. AWS Route53 hosted-zone(호스팅영역) 과 External DNS로 kubernetes service 연결하기

 


 

지난 글(https://bryan.wiki/305) 에 이어서, 이번에는 Route53에 hosted-zone을 생성하여 Local kubernetes 의 external-dns를 통해 deploy 되는 service를 연결해 보고자 한다

 

 


 

 

Route53 hosted-zone 만들기

Aws console UI에서 Route53 > 호스팅 영역 > 호스팅 영역 생성' 을 통해서도 동일한 결과를 볼 수 있다

$ aws route53 create-hosted-zone --name route53.kube.click --caller-reference this-hosted-zone-is-sub-domain-of-kube-click
----------
{
    "Location": "https://route53.amazonaws.com/2013-04-01/hostedzone/REDACTEDHOSTEDZONEID",
    "HostedZone": {
        "Id": "/hostedzone/REDACTEDHOSTEDZONEID",
        "Name": "route53.kube.click.",
        "CallerReference": "this-hosted-zone-is-sub-domain-of-kube-click",
        "Config": {
            "PrivateZone": false
        },
        "ResourceRecordSetCount": 2
    },
    "ChangeInfo": {
        "Id": "/change/REDACTEDCHANGEID",
        "Status": "PENDING",
        "SubmittedAt": "2020-09-13T09:22:25.551000+00:00"
    },
    "DelegationSet": {
        "NameServers": [
            "ns-1333.redactedawsdomain.org",
            "ns-556.redactedawsdomain.net",
            "ns-1574.redactedawsdomain.co.uk",
            "ns-240.redactedawsdomain.com"
        ]
    }
}

$ aws route53 list-hosted-zones
----------
{
    "HostedZones": [
    ...
        {
            "Id": "/hostedzone/REDACTEDHOSTEDZONEID",
            "Name": "route53.kube.click.",
            "CallerReference": "this-hosted-zone-is-sub-domain-of-kube-click",
            "Config": {
                "PrivateZone": false
            },
            "ResourceRecordSetCount": 2
        }
    ]
}

 

  • Kube.click(parent domain) 에 NS record 추가
$ export AWS_ZONE_ID=$(aws route53 list-hosted-zones-by-name --output json  --dns-name route53.kube.click | jq -r '.HostedZones[0].Id' | cut -f3 -d'/')

$ aws route53 get-hosted-zone --id $AWS_ZONE_ID
----------
{
    "HostedZone": {
        "Id": "/hostedzone/REDACTEDHOSTEDZONEID",
        "Name": "route53.kube.click.",
        "CallerReference": "this-hosted-zone-is-sub-domain-of-kube-click",
        "Config": {
            "PrivateZone": false
        },
        "ResourceRecordSetCount": 3
    },
    "DelegationSet": {
        "NameServers": [
            "ns-1333.redactedawsdomain.org",
            "ns-556.redactedawsdomain.net",
            "ns-1574.redactedawsdomain.co.uk",
            "ns-240.redactedawsdomain.com"
        ]
    }
}

 

Route53 > 호스팅 영역 > kube.click > 레코드 생성

 

  • NS resolving 확인
$ dig route53.kube.click ns
----------
; <<>> DiG 9.10.6 <<>> route53.kube.click ns
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 64026
;; flags: qr rd ra; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;route53.kube.click.        IN    NS

;; ANSWER SECTION:
route53.kube.click.    30    IN    NS    ns-1333.redactedawsdomain.org.
route53.kube.click.    30    IN    NS    ns-1574.redactedawsdomain.co.uk.
route53.kube.click.    30    IN    NS    ns-556.redactedawsdomain.net.
route53.kube.click.    30    IN    NS    ns-240.redactedawsdomain.com.

;; Query time: 186 msec
;; SERVER: 210.220.163.82#53(210.220.163.82)
;; WHEN: Sun Sep 13 20:04:09 KST 2020
;; MSG SIZE  rcvd: 187

 

 


Kubernetes external-dns deploy

Aws Route53 과 연결 가능한 external-dns 를 helm 을 통해서 deploy 해 보자.
txtOwnerId 로는 앞서 추출한 route53.kube.click 도메인의 ID를 사용한다

$ helm install external-dns bitnami/external-dns \
  --set provider=aws \
  --set aws.credentials.accessKey="AWSSDKACCESSKEY" \
  --set aws.credentials.secretKey="AWSSDKSECRETKEY" \
  --set txtOwnerId=$AWS_ZONE_ID \
  --set domainFilters\[0\]=route53.kube.click \
  --set policy=sync

$ kubectl logs external-dns-f44b659f8-vqc94
----------
...
time="2020-09-13T16:04:08Z" level=info msg="Instantiating new Kubernetes client"
time="2020-09-13T16:04:08Z" level=info msg="Using inCluster-config based on serviceaccount-token"
time="2020-09-13T16:04:08Z" level=info msg="Created Kubernetes client https://10.100.0.1:443"
time="2020-09-13T16:04:17Z" level=info msg="All records are already up to date"
time="2020-09-13T16:05:19Z" level=info msg="All records are already up to date"

 


접속 대상인 Kubernetes service deploy

Nginx 로 이루어진 kubernetes service 를 다음과 같이 정의하자. Type을 LoadBalancer 로 하고 svc1 서비스에 대해 Kubernetes cluster 에서 사용 가능한 주소 대역이 할당되도록 설정하면 되는데, 여기서 보이는 10.0.0.* 대역의 IP가 지금은 사설 IP 대역이지만 실제 운영시에는 공인 IP 대역으로, 외부 접근 가능한 주소로 보면 된다.

 

service-example.yaml

apiVersion: v1
kind: Service
metadata:
  name: svc1
  annotations:
    external-dns.alpha.kubernetes.io/hostname: svc1.route53.kube.click
  labels:
    app: nginx
spec:
  type: LoadBalancer
  ports:
  - port: 80
    name: http
    targetPort: 80
  selector:
    app: nginx

---

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx
        name: nginx
        ports:
        - containerPort: 80
          name: http

 

$ kubectl apply -f service-example.yaml
----------
service/svc1 created
deployment.apps/nginx created

$ kubectl get service -l app=nginx
----------
NAME   TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
svc1   LoadBalancer   10.100.89.153   10.0.0.201    80:32114/TCP   29m

 

약간의 시간이 흐른 후 External-dns 의 log 를 살펴 보면 다음과 같은 내용과 함께, Route53 에 새로운 A 레코드와 TXT 레코드가 추가되 어 있는 것을 볼 수 있다.

$ kubectl logs external-dns-f44b659f8-vqc94
----------
...
time="2020-09-13T16:12:28Z" level=info msg="Desired change: CREATE svc1.route53.kube.click A [Id: /hostedzone/REDACTEDHOSTEDZONEID]"
time="2020-09-13T16:12:28Z" level=info msg="Desired change: CREATE svc1.route53.kube.click TXT [Id: /hostedzone/REDACTEDHOSTEDZONEID]"
time="2020-09-13T16:12:28Z" level=info msg="2 record(s) in zone route53.kube.click. [Id: /hostedzone/REDACTEDHOSTEDZONEID] were successfully updated"
...

$ aws route53 list-resource-record-sets --hosted-zone-id $AWS_ZONE_ID
----------
{
    "ResourceRecordSets": [
        ...
        {
            "Name": "svc1.route53.kube.click.",
            "Type": "A",
            "TTL": 300,
            "ResourceRecords": [
                {
                    "Value": "10.0.0.201"
                }
            ]
        },
        {
            "Name": "svc1.route53.kube.click.",
            "Type": "TXT",
            "TTL": 300,
            "ResourceRecords": [
                {
                    "Value": "\"heritage=external-dns,external-dns/owner=REDACTEDHOSTEDZONEID,external-dns/resource=service/default/svc1\""
                }
            ]
        }
    ]
}

 


Curl 또는 웹 브라우저로 도메인 접속

$ curl http://10.0.0.201
----------
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

 


Kubernetes service 삭제

 

External-dns deploy 옵션에서 policy=sync 로 설정했기 때문에, 접속 대상이 되는 service를 삭제하면 Route53의 route53.kube.click 호스팅 영역의 svc1.route53.kube.click 레코드도 자동으로 삭제된다

$ kubectl delete -f service-example.yaml
----------
service/svc1 deleted
deployment.apps/nginx deleted

 

약간의 시간이 흐른 후 External-dns 의 log 를 살펴 보면 다음과 같은 내용과 함께, Route53 의 svc1에 대한 A 레코드와 TXT 레코드가 삭제되어 있는 것을 볼 수 있다.

$ kubectl logs external-dns-f44b659f8-vqc94
----------
...
time="2020-09-13T17:46:16Z" level=info msg="Desired change: DELETE svc1.route53.kube.click A [Id: /hostedzone/REDACTEDHOSTEDZONEID]"
time="2020-09-13T17:46:16Z" level=info msg="Desired change: DELETE svc1.route53.kube.click TXT [Id: /hostedzone/REDACTEDHOSTEDZONEID]"
time="2020-09-13T17:46:16Z" level=info msg="2 record(s) in zone route53.kube.click. [Id: /hostedzone/REDACTEDHOSTEDZONEID] were successfully updated"
...

$ aws route53 list-resource-record-sets --hosted-zone-id $AWS_ZONE_ID

 

다음 포스팅(https://bryan.wiki/307)에서는 Google cloud DNS와 external-dns 를 이용해서 kubernetes service 와 domain 주소를 통한 연결을 구성해 보도록 하자.

 

Barracuda

 

블로그 이미지

Barracuda

Bryan의 Tech-Log. 기록은 역사다. 나는 역사를 공유하고 그 안에서 배우며, 또 다른 역사를 써나간다

댓글을 달아 주세요

 

1. AWS Route53 도메인을 parent domain으로 Google cloud DNS sub-domain 등록 활용하기

 

 


이번에는 하나의 목표를 위한 3개의 글로 이루어진 시리즈물을 포스팅을 해 보려 한다.

최종 목표는 Public cloud의 managed DNS 서비스를 통해, 특정 도메인(FQDN, 여기서는 본인이 보유 중인 Route53 managed 도메인 중 kube.click 이라는 도메인의 subdomain 을 route53.kube.click 또는 gcloud.kube.click로 만들어서 사용)의 서브도메인이 자동으로 만들어지고, 해당 도메인을 통해 Kubernetes service로 외부에서 접속 가능하도록 구성해 보는 것이다. 즉, kubernetes service(또는 ingress host) 이름을 svc1 이라고 하면 svc1.route53.kube.click 으로 A record 가 생성되고 kubernetes 내에서 svc1 이 삭제되면 이 A record 도 삭제되도록 하면 상당히 간편하게 자동화 된 외부 노출 주소 관리가 가능해질 것이다.

이 기능은, 마치 OpenShift 로 app을 expose하면 app.openshift.base.domain 으로 그 app의 endpoint 가 자동으로 만들어지는 것과 유사한 사용자 경험을 제공하게 되는데, 그를 위해 현존 거의 대다수 잘 알려진 23개 Public DNS 들과 잘 연동되는 external-dns(CNCF incubation project; https://github.com/kubernetes-sigs/external-dns) 을 적용해 보고자 한다.

물론 Public cloud로는 제목에 언급된 Route53과 Google cloud DNS 각각에 대해 모두 다루어 볼 것이며, 이번 글은 다음의 2개의 글에 대한 준비 과정으로 보면 될 듯하다. 보너스로, Route53 에서 관리되는 하나의 도메인을 parent 로 해서, Google cloud DNS에 그 subdomain 을 hosted zone으로 등록하고 사용하는 과정과 방법을 같이 기록 정리해 두고자 한다.

 


  •  Pre-requisites
    • manageable FQDN(Route53 - kube.click)
    • aws sdk
    • gcloud sdk

Route53 domain 정보(parent domain)

$ aws route53 list-hosted-zones
----------
{
    "HostedZones": [
    ...
        {
            "Id": "/hostedzone/REDACTEDHOSTEDZONEID",
            "Name": "kube.click.",
            "CallerReference": "RISWorkflow-RD:REDACTEDUUID",
            "Config": {
                "Comment": "HostedZone created by Route53 Registrar",
                "PrivateZone": false
            },
            "ResourceRecordSetCount": 2
        }
    ]
}

 


Google cloud project 및 managed zone 생성

$ export PROJECT_NAME="dns-hosting-poc"
$ gcloud config set project $PROJECT_NAME

$ export CUSTOM_DOMAIN="gcloud.kube.click"
$ export DNS_ZONE_NAME="subdomain-route53"

$ gcloud dns managed-zones create $DNS_ZONE_NAME \
    --dns-name $CUSTOM_DOMAIN \
    --description "Automatically managed zone by kubernetes.io/external-dns"
----------
Created [https://dns.googleapis.com/dns/v1/projects/dns-hosting-poc/managedZones/subdomain-route53].

$ gcloud dns record-sets list \
    --zone=$DNS_ZONE_NAME \
    --name=$CUSTOM_DOMAIN
----------
NAME             TYPE  TTL    DATA
gcloud.kube.click.  NS    21600  ns-cloud-e1.googledomains.com.,ns-cloud-e2.googledomains.com.,ns-cloud-e3.googledomains.com.,ns-cloud-e4.googledomains.com.
gcloud.kube.click.  SOA   21600  ns-cloud-e1.googledomains.com. cloud-dns-hostmaster.google.com. 1 21600 3600 259200 300

 


Route53 parent domain에 NS record 추가

  • 변경 전 record-sets 확인

    $ export AWS_ZONE_ID=$(aws route53 list-hosted-zones-by-name --output json  --dns-name kube.click | jq -r '.HostedZones[0].Id' | cut -f3 -d'/')
    $ aws route53 list-resource-record-sets --hosted-zone-id $AWS_ZONE_ID

 

  • NS record 추가(단순 레코드 정의)

     

 

 

  • 변경 후 record-sets 확인

    $ export AWS_ZONE_ID=$(aws route53 list-hosted-zones-by-name --output json  --dns-name kube.click | jq -r '.HostedZones[0].Id' | cut -f3 -d'/')
    $ aws route53 list-resource-record-sets --hosted-zone-id $AWS_ZONE_ID

 

  • NS resolving 확인

    $ dig gcloud.kube.click ns
    ----------
    ; <<>> DiG 9.10.6 <<>> gcloud.kube.click ns
    ;; global options: +cmd
    ;; Got answer:
    ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 20417
    ;; flags: qr rd ra; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: 1
    
    ;; OPT PSEUDOSECTION:
    ; EDNS: version: 0, flags:; udp: 4096
    ;; QUESTION SECTION:
    ;gcloud.kube.click.		IN	NS
    
    ;; ANSWER SECTION:
    gcloud.kube.click.	300	IN	NS	ns-cloud-e2.googledomains.com.
    gcloud.kube.click.	300	IN	NS	ns-cloud-e4.googledomains.com.
    gcloud.kube.click.	300	IN	NS	ns-cloud-e3.googledomains.com.
    gcloud.kube.click.	300	IN	NS	ns-cloud-e1.googledomains.com.
    
    ;; Query time: 1007 msec
    ;; SERVER: 210.220.163.82#53(210.220.163.82)
    ;; WHEN: Mon Sep 14 03:10:31 KST 2020
    ;; MSG SIZE  rcvd: 167
    

 


Google cloud DNS에 A record 등록 후 resolving 확인

 

 

$ gcloud dns record-sets list --zone=subdomain-route53
----------
NAME                     TYPE  TTL    DATA
gcloud.kube.click.       NS    21600  ns-cloud-e1.googledomains.com.,ns-cloud-e2.googledomains.com.,ns-cloud-e3.googledomains.com.,ns-cloud-e4.googledomains.com.
gcloud.kube.click.       SOA   21600  ns-cloud-e1.googledomains.com. cloud-dns-hostmaster.google.com. 1 21600 3600 259200 300
test.gcloud.kube.click.  A     30     192.2.0.91

$ host test.gcloud.kube.click
----------
test.gcloud.kube.click has address 192.2.0.91

 

다음 포스팅(https://bryan.wiki/306)에서는 Route53 DNS와 external-dns 를 이용해서 kubernetes service 와 domain 주소를 통한 연결을 구성해 보도록 하자.

 

Barracuda

블로그 이미지

Barracuda

Bryan의 Tech-Log. 기록은 역사다. 나는 역사를 공유하고 그 안에서 배우며, 또 다른 역사를 써나간다

댓글을 달아 주세요

Setting GOPATH & Basic golang 개발

Refer 1: SettingGOPATH · golang/go Wiki · GitHub

Refer 2: Go 코드를 작성하는 방법 · golang-kr/golang-doc Wiki · GitHub

특별히 공유 목적을 위해 만든 글은 아니다. 그냥 예전에 했던 설정 방식이 기억 나지 않아 구글링을 하다가 리프레시해둬야 겠다는 단순한 의도로 끄적 거려 둠. 그러나 누군가에겐 조금의 도움이 되었기를 ...


Zsh

  • Add to ~/.zshrc

    export GOPATH=$HOME/go
  • source it

    source ~/.zshrc

Bash

  • Add to ~/.bash_profile

    export GOPATH=$HOME/go
  • source it

    source ~/.bash_profile
블로그 이미지

Barracuda

Bryan의 Tech-Log. 기록은 역사다. 나는 역사를 공유하고 그 안에서 배우며, 또 다른 역사를 써나간다

Tag Bash, GO, golang, macos, zsh

댓글을 달아 주세요


  • MacOS 기본 터미널 bash command-line
    • ⌥ + ← 또는 ⌥ + → : 커서를 왼쪽 단어 또는 오른쪽 단어로
    • ⌘ + ← 또는 ⌘ + → : 커서를 start 또는 end of line 으로
  • zsh 의 경우
    • ESC B 또는 ESC F : 커서를 왼쪽 또는 오른쪽 단어로 이동(불편하기 짝이 없음)
    • ⌃ + a 또는 ⌃ + e : 커서를 start 또는 end of line 으로

bash 기본 터미널, zsh 각각에서 별도 설정을 하지 않았을 경우 위의 스타일 대로 커서가 이동 된다. 본인은 MacOS Catalina에서 'Oh My Zsh' 와 iterm2 를 사용중인데, 아무래도 ⌥ + ← 또는 ⌘ + ← 방식에 익숙해져 있어서 다음의 방법대로 설정 사용중이다.


zsh에서 MacOS 기본 bash 터미널 커서 움직임과 동일하게 설정하는 방법

  1. .zshrc에 아래 line 들 추가

    bindkey "[D" backward-word
    bindkey "[C" forward-word
    bindkey "^[a" beginning-of-line
    bindkey "^[e" end-of-line
  2. iterm2의 preferences > Keys > Key Bindings

    • ⌘ ← > Action: Send Escape Sequence 에 a 입력 > OK
    • ⌘ → > Action: Send Escape Sequence 에 e 입력 > OK

Key 바인딩을 하고 나면 스크린 캡처에서 표시된 것 처럼 확인 가능하다.

위 1, 2의 과정을 거쳐 설정을 완료하면 되고, 단어 사이의 이동인  ⌥ + ← 또는 ⌥ + → 은 1번 과정 하나 만으로 충분해서 2번의 별도 Key 바인딩이 필요 없다. 다만 경고 beep 음이 약간 거슬릴 수 있는데, 이건 세부 설정을 따로 하면 쉽게 해결될 수 있을 듯.

- Barracuda -


블로그 이미지

Barracuda

Bryan의 Tech-Log. 기록은 역사다. 나는 역사를 공유하고 그 안에서 배우며, 또 다른 역사를 써나간다

댓글을 달아 주세요


이번 포스팅에서는 Statefulset 형태로 구현이 가능한 대표적인 클러스터인 Galera Cluster를 다루어 볼 것이다. 그 중에서도 Galera Cluster의 각 노드(Pod)의 상태를 etcd 데이터 스토어에 기록하고 사용할 수 있도록 etcd cluster를 동시에 활용하는 방식을 적용해 보려 한다. 주요 참조 기술 및 블로그는 글 말미의 Reference 를 참조하기 바란다.  앞서의 포스팅들과는 다르게 여기서는 Kubernetes 의 현 시점의 최신 버전인 1.11.3 을 활용하였다.



Mariadb Galera Cluster 구현을 위한 준비와 개념도


[Prerequisites]


  • Running k8s cluster with persistent storage(glusterfs or nfs storageclass, etc.)
  • Tested kubernetes version: 1.9.x, 1.11.x


Kubernetes 상에서 동작하는 Galera Cluster Application의 구조는 다음의 그림에 나타나 있다


 


  • galera-cluster-etcd-k8s-mod 프로젝트 clone
먼저 프로젝트의 manifest 를 clone 또는 다운로드해 둔다. 아래 github 프로젝트를 브라우저로 열고 내용을 확인하면서 개별 작업해도 무방
 

# git clone https://github.com/DragOnMe/galera-cluster-etcd-k8s-mod.git

# cd galera-cluster-etcd-k8s-mod.git


  • Container image re-building

docker.io/severalnines/mariadb 이미지도 역시 훌륭하게 동작하지만, 한글 데이터, 소문자 테이블명 등의 처리가 가능하도록 하기 위해서,  해당 이미지의 빌드 소스 위치인 github.com/severalnines/galera-docker-mariadb 레포지터리의 my.cnf 설정 파일을 변경하여 새로운 이미지를 빌드 & 푸쉬해서 사용하도록 한다.


여기서는 drlee001 대신에 github.com 사이트에 본인 각자의 account 를 발급 받아 사용하면 된다


# cd galera-docker-mariadb

# docker build -t  drlee001/mariadb:10.1-modcnf .

# docker login -u drlee001

# docker push  drlee001/mariadb:10.1-modcnf



ETCD와 Galera Cluster 구축


  • etcd cluster 생성 및 점검

Galera Cluster 의 각 Mariadb node 상태 정보가 etcd store에 실시간 저장하고 관리되므로 가장 먼저 etcd 저장소를 구축해야 한다. 해당 아키텍처는 위 그림에 표현되어 있으니 참고하자.


실제로는 모든 galera 노드들이 자신의 상태 값을 주기적으로 etcd에 저장하며, 이 값들은 일정 시간이 지나면(TTL: 10초) 자동으로 expire 되는 식으로 동작한다.


# cd ..

# kubectl config set-context $(kubectl config current-context) --namespace=ns-galera-etcd

Context "kubernetes-admin@kubernetes" modified.


# kubectl create -f 00-etcd-cluster.yaml 

namespace/ns-galera-etcd created

service/etcd-client created

pod/etcd0 created

service/etcd0 created

pod/etcd1 created

service/etcd1 created

pod/etcd2 created

service/etcd2 created


# kubectl get pod,svc

NAME        READY     STATUS    RESTARTS   AGE

pod/etcd0   1/1       Running   0          4m

pod/etcd1   1/1       Running   0          4m

pod/etcd2   1/1       Running   0          4m


NAME                  TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)             AGE

service/etcd-client   ClusterIP   10.134.158.165   <none>        2379/TCP            4m

service/etcd0         ClusterIP   10.142.79.41     <none>        2379/TCP,2380/TCP   4m

service/etcd1         ClusterIP   10.136.241.253   <none>        2379/TCP,2380/TCP   4m

service/etcd2         ClusterIP   10.128.106.181   <none>        2379/TCP,2380/TCP   4m


# curl -L http://10.134.158.165:2379/version

{"etcdserver":"3.3.8","etcdcluster":"3.3.0"}


# kubectl exec -it etcd0 -- etcdctl cluster-health

member ade526d28b1f92f7 is healthy: got healthy result from http://etcd1:2379

member cf1d15c5d194b5c9 is healthy: got healthy result from http://etcd0:2379

member d282ac2ce600c1ce is healthy: got healthy result from http://etcd2:2379

cluster is healthy


# kubectl exec -it etcd0 -- etcdctl --version

etcdctl version: 3.3.8

API version: 2

* ETCD의 API 서버로 쿼리를 보내는 방법은 위에 나온 것 처럼 etcdclient 서비스로 curl 명령을 통해서 API 호출을 하거나 또는 각 etcd pod 중 하나로 직접 접속해서 etcdctl 명령을 보내는 2가지 방법이 있다 



  • Galera Cluster 의 구축 및 etcd 데이터 확인

# kubectl create -f 01-galera-mariadb-ss.yaml 

service/galera-hs created

statefulset.apps/galera-ss created


# kubectl get pods -w

NAME          READY     STATUS    RESTARTS   AGE

etcd0         1/1       Running   0          20m

etcd1         1/1       Running   0          20m

etcd2         1/1       Running   0          20m

galera-ss-0   0/1       Running   0          38s

galera-ss-0   1/1       Running   0         2m

galera-ss-1   0/1       Pending   0         0s

galera-ss-1   0/1       Pending   0         0s

galera-ss-1   0/1       Pending   0         8s

galera-ss-1   0/1       ContainerCreating   0         8s

galera-ss-1   0/1       Running   0         11s

galera-ss-0   0/1       Running   0         3m

galera-ss-0   1/1       Running   0         3m

galera-ss-1   1/1       Running   0         2m

galera-ss-2   0/1       Pending   0         0s

galera-ss-2   0/1       Pending   0         0s

galera-ss-2   0/1       Pending   0         19s

galera-ss-2   0/1       ContainerCreating   0         19s

galera-ss-2   0/1       Running   0         24s

galera-ss-0   0/1       Running   0         6m

galera-ss-0   1/1       Running   0         6m

galera-ss-2   0/1       Running   1         2m

galera-ss-2   1/1       Running   1         4m


# kubectl get pod,svc

NAME              READY     STATUS    RESTARTS   AGE

pod/etcd0         1/1       Running   0          32m

pod/etcd1         1/1       Running   0          32m

pod/etcd2         1/1       Running   0          32m

pod/galera-ss-0   1/1       Running   0          12m

pod/galera-ss-1   1/1       Running   0          10m

pod/galera-ss-2   1/1       Running   1          8m


NAME                                                  TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)             AGE

service/etcd-client                                   ClusterIP   10.134.158.165   <none>        2379/TCP            32m

service/etcd0                                         ClusterIP   10.142.79.41     <none>        2379/TCP,2380/TCP   32m

service/etcd1                                         ClusterIP   10.136.241.253   <none>        2379/TCP,2380/TCP   32m

service/etcd2                                         ClusterIP   10.128.106.181   <none>        2379/TCP,2380/TCP   32m

service/galera-hs                                     ClusterIP   None             <none>        3306/TCP            12m

service/glusterfs-dynamic-mysql-datadir-galera-ss-0   ClusterIP   10.139.193.16    <none>        1/TCP               12m

service/glusterfs-dynamic-mysql-datadir-galera-ss-1   ClusterIP   10.136.64.128    <none>        1/TCP               10m

service/glusterfs-dynamic-mysql-datadir-galera-ss-2   ClusterIP   10.138.84.181    <none>        1/TCP               7m


# curl -L http://10.134.158.165:2379/v2/keys

{"action":"get","node":{"dir":true,"nodes":[{"key":"/galera","dir":true,"modifiedIndex":8,"createdIndex":8}]}}


# curl -L http://10.134.158.165:2379/v2/keys/galera/mariadb_galera_ss

{"action":"get","node":{"key":"/galera/mariadb_galera_ss","dir":true,"nodes":[{"key":"/galera/mariadb_galera_ss/10.40.0.3","dir":true,"modifiedIndex":8,"createdIndex":8},{"key":"/galera/mariadb_galera_ss/10.44.0.5","dir":true,"modifiedIndex":49,"createdIndex":49},{"key":"/galera/mariadb_galera_ss/10.32.0.6","dir":true,"modifiedIndex":129,"createdIndex":129}],"modifiedIndex":8,"createdIndex":8}}


# kubectl exec -it etcd0 -- etcdctl ls /galera/mariadb_galera_ss

/galera/mariadb_galera_ss/10.40.0.3

/galera/mariadb_galera_ss/10.44.0.5

/galera/mariadb_galera_ss/10.32.0.6

* 각 데이터 노드(pod)는 0번 부터 하나씩 순차적으로 만들어지며, 클러스터 내의 각 노드(Mariadb Pod)들 끼리는 자동으로 discover 가 이루어 진다



Galera Cluster의 데이터 동기화 점검


Galera Cluster 각 노드의 주요 상태를 조회해 보면 다음과 같다

 

# ./88-galera-test.sh 

+---------------------------+----------------+

| VARIABLE_NAME             | VARIABLE_VALUE |

+---------------------------+----------------+

| WSREP_CLUSTER_SIZE        | 3              |

| WSREP_CLUSTER_STATUS      | Primary        |

| WSREP_CONNECTED           | ON             |

| WSREP_LOCAL_INDEX         | 1              |

| WSREP_LOCAL_STATE_COMMENT | Synced         |

| WSREP_READY               | ON             |

+---------------------------+----------------+

+---------------------------+----------------+

| VARIABLE_NAME             | VARIABLE_VALUE |

+---------------------------+----------------+

| WSREP_CLUSTER_SIZE        | 3              |

| WSREP_CLUSTER_STATUS      | Primary        |

| WSREP_CONNECTED           | ON             |

| WSREP_LOCAL_INDEX         | 2              |

| WSREP_LOCAL_STATE_COMMENT | Synced         |

| WSREP_READY               | ON             |

+---------------------------+----------------+

+---------------------------+----------------+

| VARIABLE_NAME             | VARIABLE_VALUE |

+---------------------------+----------------+

| WSREP_CLUSTER_SIZE        | 3              |

| WSREP_CLUSTER_STATUS      | Primary        |

| WSREP_CONNECTED           | ON             |

| WSREP_LOCAL_INDEX         | 0              |

| WSREP_LOCAL_STATE_COMMENT | Synced         |

| WSREP_READY               | ON             |

+---------------------------+----------------+



데이터 동기화를 위해 각 데이터베이스 노드에서 데이터를 Insert, Select 및 삭제가 정상적으로 동작하고 동기화 되는지 점검한다


# ./89-galera-sync-test.sh 

1. Create database & table(node 0):

2. Insert 2 records into table(node 0):

+-------------------+----------------+

| VARIABLE_NAME     | VARIABLE_VALUE |

+-------------------+----------------+

| WSREP_LOCAL_INDEX | 1              |

+-------------------+----------------+

+------+-----------------+

| id   | name            |

+------+-----------------+

| aaa  | xxx             |

| id   | this is my name |

+------+-----------------+

3. Show data(node 1):

+-------------------+----------------+

| VARIABLE_NAME     | VARIABLE_VALUE |

+-------------------+----------------+

| WSREP_LOCAL_INDEX | 2              |

+-------------------+----------------+

+------+-----------------+

| id   | name            |

+------+-----------------+

| aaa  | xxx             |

| id   | this is my name |

+------+-----------------+

4. Show data(node 2):

+-------------------+----------------+

| VARIABLE_NAME     | VARIABLE_VALUE |

+-------------------+----------------+

| WSREP_LOCAL_INDEX | 0              |

+-------------------+----------------+

+------+-----------------+

| id   | name            |

+------+-----------------+

| aaa  | xxx             |

| id   | this is my name |

+------+-----------------+

5. Delete data(node 2):

6. Show data(node 0):

+-------------------+----------------+

| VARIABLE_NAME     | VARIABLE_VALUE |

+-------------------+----------------+

| WSREP_LOCAL_INDEX | 1              |

+-------------------+----------------+

+------+-----------------+

| id   | name            |

+------+-----------------+

| id   | this is my name |

+------+-----------------+

7. Show data(node 1):

+-------------------+----------------+

| VARIABLE_NAME     | VARIABLE_VALUE |

+-------------------+----------------+

| WSREP_LOCAL_INDEX | 2              |

+-------------------+----------------+

+------+-----------------+

| id   | name            |

+------+-----------------+

| id   | this is my name |

+------+-----------------+

8. Delete database & table(node 1):

+--------------------+

| Database           |

+--------------------+

| information_schema |

| mydatabase         |

| mysql              |

| performance_schema |

+--------------------+



Galera Cluster 의 리셋, 삭제


Galera Cluster에서 사용하는 etcd 데이터 영역을 Reset 하려면, 먼저 Galera Cluster 정보를 다음과 같이 삭제,


# curl http://10.134.158.165:2379:2379/v2/keys/galera/mariadb_galera_ss?recursive=true -XDELETE 


또는


# kubectl exec -it etcd0 -- etcdctl rm /galera/mariadb_galera_ss --recursive




만약 전체 영역을 모두 삭제하고 싶다면 아래 script 를 수행하면 된다.


# ./99-teardown.sh 

service "galera-hs" deleted

statefulset.apps "galera-ss" deleted

persistentvolumeclaim "mysql-datadir-galera-ss-0" deleted

persistentvolumeclaim "mysql-datadir-galera-ss-1" deleted

persistentvolumeclaim "mysql-datadir-galera-ss-2" deleted

namespace "ns-galera-etcd" deleted

service "etcd-client" deleted

pod "etcd0" deleted

service "etcd0" deleted

pod "etcd1" deleted

service "etcd1" deleted

pod "etcd2" deleted

service "etcd2" deleted

No resources found.

Id:6d779dbd1e08b7ff7348360190091632    Cluster:60147830250310129f7b11084613a204    Name:heketidbstorage



# kubectl config set-context $(kubectl config current-context) --namespace=default

Context "kubernetes-admin@kubernetes" modified.



Reference


  • https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/
  • https://severalnines.com/blog/mysql-docker-deploy-homogeneous-galera-cluster-etcd
  • https://github.com/severalnines/galera-docker-mariadb



- Barracuda -



블로그 이미지

Barracuda

Bryan의 Tech-Log. 기록은 역사다. 나는 역사를 공유하고 그 안에서 배우며, 또 다른 역사를 써나간다

댓글을 달아 주세요


Mongodb Replicaset을 구현하는 방법은 여러 가지(sidecar, init-container 방식 등)이 있지만, 여기서는 docker.io의 mongo 3.4 ~ mongo:3.7 까지의 범용 이미지를 사용한 Replicaset 구현 방법을 다루어 보고자 한다.


Mongodb Replicaset 구현 & 기능 검증


[Prerequisites]


  • Running k8s cluster with persistent storage(glusterfs class, etc.)
  • Tested kubernetes version: 1.9.x, 1.11.x


[Deployment resources - mongodb-service.yaml]


# Service/endpoint for load balancing the client connection from outside
# By NodePort
apiVersion: v1
kind: Service
metadata:
  namespace: ns-mongo
  name: mongodb-svc
  labels:
    role: mongo
spec:
  type: NodePort
  ports:
    - port: 27017
      name: client
      nodePort: 30017
  selector:
    role: mongors
---
# A headless service to create DNS records
apiVersion: v1
kind: Service
metadata:
  annotations:
    service.alpha.kubernetes.io/tolerate-unready-endpoints: "true"
  namespace: ns-mongo
  name: mongodb-hs
  labels:
    name: mongo
spec:
  # the list of ports that are exposed by this service
  ports:
    - port: 27017
      name: mongodb
      targetPort: 27017
  clusterIP: None
  selector:
    role: mongors
---
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
  namespace: ns-mongo
  name: mongod-ss
spec:
  serviceName: mongodb-hs
  replicas: 3
  template:
    metadata:
      labels:
        role: mongors
        environment: test
        replicaset: MainRepSet
    spec:
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: replicaset
                  operator: In
                  values:
                  - MainRepSet
              topologyKey: kubernetes.io/hostname
      terminationGracePeriodSeconds: 10
      volumes:
        - name: secrets-volume
          secret:
            secretName: shared-bootstrap-data
            defaultMode: 256
      containers:
        - name: mongod-container
          # Notice
          # Tested on mongo:3.4, 3.6, 3.7
          # as to mongo:3.2, an error happens like below:
          # Error parsing option "wiredTigerCacheSizeGB" as int: Bad digit "." while parsing 0.25
          image: mongo:3.4
          command:
            - "numactl"
            - "--interleave=all"
            - "mongod"
            - "--wiredTigerCacheSizeGB"
            - "0.25"
            - "--bind_ip"
            - "0.0.0.0"
            - "--replSet"
            - "MainRepSet"
            - "--auth"
            - "--clusterAuthMode"
            - "keyFile"
            - "--keyFile"
            - "/etc/secrets-volume/internal-auth-mongodb-keyfile"
            - "--setParameter"
            - "authenticationMechanisms=SCRAM-SHA-1"
          resources:
            requests:
              cpu: 0.3
              memory: 128Mi
          ports:
            - containerPort: 27017
          volumeMounts:
            - name: secrets-volume
              readOnly: true
              mountPath: /etc/secrets-volume
            - name: mongodb-pv-claim
              mountPath: /data/db
  volumeClaimTemplates:
    - metadata:
        namespace: ns-mongo
        name: mongodb-pv-claim
        annotations:
          volume.beta.kubernetes.io/storage-class: glusterfs-storage
      spec:
        accessModes: [ "ReadWriteOnce" ]
        resources:
          requests:
            storage: 400Mi

* Service(NodePort), headless service, statefulset 으로 구성



[Deployment steps]


  • Statefulset deployment

# git clone https://github.com/DragOnMe/mongo-statefulset-glusterfs.git

# cd mongo-statefulset-gluster-pv

# ./01-generate_mongo_ss.sh

namespace "ns-mongo" created

secret "shared-bootstrap-data" created

service "mongodb-svc" created

service "mongodb-hs" created

statefulset "mongod-ss" created


Waiting for the 3 containers to come up (2018. 09. 16. (일) 17:19:37 KST)...

 (IGNORE any reported not found & connection errors)

  Error from server (NotFound): pods "mongod-ss-2" not found

  ...

  Error from server (NotFound): pods "mongod-ss-2" not found

  error: unable to upgrade connection: container not found ("mongod-container")

  ...

  error: unable to upgrade connection: container not found ("mongod-container")

  connection to 127.0.0.1:27017

...mongod containers are now running (TIMESTAMP)


deployment "mongo-client" created

NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS    CLAIM                                   STORAGECLASS        REASON    AGE

pvc-17853524-b98a-11e8-8c49-080027f6d038   1G         RWO            Delete           Bound     ns-mongo/mongodb-pv-claim-mongod-ss-1   glusterfs-storage             3m

pvc-1cea12e2-b98a-11e8-8c49-080027f6d038   1G         RWO            Delete           Bound     ns-mongo/mongodb-pv-claim-mongod-ss-2   glusterfs-storage             3m

pvc-407bdb23-b989-11e8-8c49-080027f6d038   1G         RWO            Delete           Bound     ns-mongo/mongodb-pv-claim-mongod-ss-0   glusterfs-storage             9m


NAME                                                 TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)           AGE

svc/glusterfs-dynamic-mongodb-pv-claim-mongod-ss-0   ClusterIP   10.140.100.198   <none>        1/TCP             9m

svc/glusterfs-dynamic-mongodb-pv-claim-mongod-ss-1   ClusterIP   10.136.50.38     <none>        1/TCP             3m

svc/glusterfs-dynamic-mongodb-pv-claim-mongod-ss-2   ClusterIP   10.135.150.63    <none>        1/TCP             3m

svc/mongodb-hs                                       ClusterIP   None             <none>        27017/TCP         9m

svc/mongodb-svc                                      NodePort    10.138.204.207   <none>        27017:30017/TCP   9m


NAME                     DESIRED   CURRENT   AGE

statefulsets/mongod-ss   3         3         9m


NAME                               STATUS    VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS        AGE

pvc/mongodb-pv-claim-mongod-ss-0   Bound     pvc-407bdb23-b989-11e8-8c49-080027f6d038   1G         RWO            glusterfs-storage   9m

pvc/mongodb-pv-claim-mongod-ss-1   Bound     pvc-17853524-b98a-11e8-8c49-080027f6d038   1G         RWO            glusterfs-storage   3m

pvc/mongodb-pv-claim-mongod-ss-2   Bound     pvc-1cea12e2-b98a-11e8-8c49-080027f6d038   1G         RWO            glusterfs-storage   3m

Waiting for rollout to finish: 0 of 1 updated replicas are available...

deployment "mongo-client" successfully rolled out

* 3개의 mongod pod가 순차적으로 만들어지며, 마지막 2 번 pod가만들어지면 결과를 보여줌

* 생성이 진행되는 동안 Error from server, ... unable to upgrade connection 과 같은 오류가 발생하지만 종료될 때까지 무시


# kubectl get pods -n ns-mongo 

NAME                           READY     STATUS              RESTARTS   AGE

mongo-client-799dc789b-p8kgv   1/1       Running             0          10m

mongod-ss-0                    1/1       Running             0          36m

mongod-ss-1                    1/1       Running             0          30m

mongod-ss-2                    0/1       ContainerCreating   0          4s


# kubectl logs -f -n ns-mongo mongod-ss-0

2018-09-16T09:11:09.843+0000 I CONTROL  [initandlisten] MongoDB starting : pid=1 port=27017 dbpath=/data/db 64-bit host=mongod-ss-0

2018-09-16T09:11:09.844+0000 I CONTROL  [initandlisten] db version v3.4.17

...

2018-09-16T09:11:11.409+0000 I CONTROL  [initandlisten] ** WARNING: /sys/kernel/mm/transparent_hugepage/defrag is 'always'.

2018-09-16T09:11:11.409+0000 I CONTROL  [initandlisten] **        We suggest setting it to 'never'

2018-09-16T09:11:11.834+0000 I REPL     [initandlisten] Did not find local replica set configuration document at startup;  NoMatchingDocument: Did not find replica set configuration document in local.system.replset

2018-09-16T09:11:11.838+0000 I NETWORK  [thread1] waiting for connections on port 27017


# kubectl logs -f -n ns-mongo mongod-ss-1

2018-09-16T09:11:18.847+0000 I CONTROL  [initandlisten] MongoDB starting : pid=1 port=27017 dbpath=/data/db 64-bit host=mongod-ss-1

...

2018-09-16T09:11:20.808+0000 I NETWORK  [thread1] waiting for connections on port 27017


# kubectl logs -f -n ns-mongo mongod-ss-2

2018-09-16T09:11:25.977+0000 I CONTROL  [initandlisten] MongoDB starting : pid=1 port=27017 dbpath=/data/db 64-bit host=mongod-ss-2

...

2018-09-16T09:11:27.538+0000 I CONTROL  [initandlisten] ** WARNING: You are running this process as the root user, which is not recommended.

2018-09-16T09:11:27.539+0000 I CONTROL  [initandlisten] ** WARNING: /sys/kernel/mm/transparent_hugepage/defrag is 'always'.

...

2018-09-16T09:11:27.878+0000 I FTDC     [initandlisten] Initializing full-time diagnostic data capture with directory '/data/db/diagnostic.data'

2018-09-16T09:11:28.224+0000 I REPL     [initandlisten] Did not find local voted for document at startup.

2018-09-16T09:11:28.224+0000 I REPL     [initandlisten] Did not find local replica set configuration document at startup;  NoMatchingDocument: Did not find replica set configuration document in local.system.replset

2018-09-16T09:11:28.226+0000 I NETWORK  [thread1] waiting for connections on port 27017

...

2018-09-16T09:11:32.214+0000 I -        [conn1] end connection 127.0.0.1:53452 (1 connection now open)

* Replica set 의 구성 대상인 3개의 mongod pod가 위와 같은 대기상태의 메시지 log(...waiting for connection..) 를 보일 때까지 대기


  • Replicaset 초기화 및 mongod admin 계정 생성

# ./02-configure_repset_auth.sh abc123

Configuring the MongoDB Replica Set

MongoDB shell version v3.4.17

connecting to: mongodb://127.0.0.1:27017

MongoDB server version: 3.4.17

{ "ok" : 1 }


Waiting for the MongoDB Replica Set to initialise...

MongoDB shell version v3.4.17

connecting to: mongodb://127.0.0.1:27017

MongoDB server version: 3.4.17

.

.

...initialisation of MongoDB Replica Set completed


Creating user: 'main_admin'

MongoDB shell version v3.4.17

connecting to: mongodb://127.0.0.1:27017

MongoDB server version: 3.4.17

Successfully added user: {

"user" : "main_admin",

"roles" : [

{

"role" : "root",

"db" : "admin"

}

]

}

* Replicset 구성을 수행하도록 하고, 데이터베이스 암호는 abc123 으로 설정

* Mongodb Replicaset 의 구현이 완료되었다. 이제 Replicaset 을 인식하는 클러스터 내의 다른 앱에서 각 mongod pod에 아래와 같은 URI 로 접속이 가능하다. 예를 들어 pymongo 의 경우 mongodb://mongod-ss-0.mongodb-hs.ns-mongo.svc.cluster.local:27017,mongod-ss-1.mongodb-hs.ns-mongo.svc.cluster.local:27017,mongod-ss-2.mongodb-hs.ns-mongo.svc.cluster.local:27017/?replicaSet=test 와 같은 형식으로 접속 가능


  - mongod-ss-0.mongodb-hs.ns-mongo.svc.cluster.local:27017

  - mongod-ss-1.mongodb-hs.ns-mongo.svc.cluster.local:27017

  - mongod-ss-2.mongodb-hs.ns-mongo.svc.cluster.local:27017



[How to check if things are working properly]


  • Replicaset member, mongod 간 데이터 동기화

# export MONGOD_NAMESPACE="ns-mongo"

# export MONGO_CLIENT=`kubectl get pods -n $MONGOD_NAMESPACE | grep mongo-client | awk '{print $1}'`

# kubectl exec -it -n $MONGOD_NAMESPACE $MONGO_CLIENT -- mongo mongodb://mongod-ss-0.mongodb-hs.ns-mongo.svc.cluster.local:27017

MongoDB shell version v3.4.2

connecting to: mongodb://mongod-ss-0.mongodb-hs.ns-mongo.svc.cluster.local:27017

MongoDB server version: 3.4.17

MainRepSet:PRIMARY> db.getSiblingDB('admin').auth("main_admin", "abc123");

1

MainRepSet:PRIMARY> use test;

switched to db test

MainRepSet:PRIMARY> db.testcoll.insert({a:1});

WriteResult({ "nInserted" : 1 })

MainRepSet:PRIMARY> db.testcoll.insert({b:2});

WriteResult({ "nInserted" : 1 })

MainRepSet:PRIMARY> db.testcoll.find();

{ "_id" : ObjectId("5b9fd8f0bc9812b50016a157"), "a" : 1 }

{ "_id" : ObjectId("5b9fd8f8bc9812b50016a158"), "b" : 2 }

* Primary(0 번)에 접속하여 데이터 입력, 저장


# kubectl exec -it -n $MONGOD_NAMESPACE $MONGO_CLIENT -- mongo mongodb://mongod-ss-1.mongodb-hs.ns-mongo.svc.cluster.local:27017

MongoDB shell version v3.4.2

connecting to: mongodb://mongod-ss-1.mongodb-hs.ns-mongo.svc.cluster.local:27017

MongoDB server version: 3.4.17

MainRepSet:SECONDARY> db.getSiblingDB('admin').auth("main_admin", "abc123");

1

MainRepSet:SECONDARY> use test;

switched to db test

MainRepSet:SECONDARY> db.setSlaveOk(1);

MainRepSet:SECONDARY> db.testcoll.find();

{ "_id" : ObjectId("5b9fd8f0bc9812b50016a157"), "a" : 1 }

{ "_id" : ObjectId("5b9fd8f8bc9812b50016a158"), "b" : 2 }

* Secondary(1번과 2번)에 접속하여  데이터 동기화 확인



  • Primary Pod 삭제 후, ReplicaSet 및 데이터 유지 확인
# kubectl get pod -n ns-mongo -o wide
NAME                           READY     STATUS    RESTARTS   AGE       IP          NODE
mongo-client-799dc789b-6rgvv   1/1       Running   0          1d        10.38.0.8   kubenode3
mongod-ss-0                    1/1       Running   6          1d        10.40.0.5   kubenode2
mongod-ss-1                    1/1       Running   6          1d        10.38.0.2   kubenode3
mongod-ss-2                    1/1       Running   0          1d        10.38.0.7   kubenode3

# kubectl delete pod -n ns-mongo mongod-ss-0
pod "mongod-ss-0" deleted

# kubectl get pod -n ns-mongo -o wide
NAME                           READY     STATUS    RESTARTS   AGE       IP          NODE
mongo-client-799dc789b-6rgvv   1/1       Running   0          1d        10.38.0.8   kubenode3
mongod-ss-0                    1/1       Running   0          4s        10.40.0.5   kubenode2
mongod-ss-1                    1/1       Running   6          1d        10.38.0.2   kubenode3
mongod-ss-2                    1/1       Running   0          1d        10.38.0.7   kubenode3

# kubectl exec -it -n $MONGOD_NAMESPACE $MONGO_CLIENT -- mongo mongodb://mongod-ss-0.mongodb-hs.ns-mongo.svc.cluster.local:27017
MongoDB shell version v3.4.2
connecting to: mongodb://mongod-ss-0.mongodb-hs.ns-mongo.svc.cluster.local:27017
MongoDB server version: 3.4.17
MainRepSet:SECONDARY> db.getSiblingDB('admin').auth("main_admin", "abc123");
1
MainRepSet:SECONDARY> use test;
switched to db test
MainRepSet:SECONDARY> db.setSlaveOk(1);
MainRepSet:SECONDARY> db.testcoll.find();
{ "_id" : ObjectId("5b9fd8f0bc9812b50016a157"), "a" : 1 }
{ "_id" : ObjectId("5b9fd8f8bc9812b50016a158"), "b" : 2 }

# kubectl exec -it -n $MONGOD_NAMESPACE $MONGO_CLIENT -- mongo mongodb://mongod-ss-2.mongodb-hs.ns-mongo.svc.cluster.local:27017
MongoDB shell version v3.4.2
connecting to: mongodb://mongod-ss-2.mongodb-hs.ns-mongo.svc.cluster.local:27017
MongoDB server version: 3.4.17
MainRepSet:PRIMARY>
* mongod-ss-0 을 삭제한 후 mongod-ss-0은 다시 되살아 났으며(데이터는 유지), mongod-ss-2가 Primary로 승격되었다. 결과적으로 Replicaset은 계속 유지

  • 서비스 삭제 후 재생성, DB 데이터 유지 확인

# ./03-delete_service.sh 

statefulset "mongod-ss" deleted

service "mongodb-hs" deleted

NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS    CLAIM                                   STORAGECLASS        REASON    AGE

pvc-6eb5c4f7-b990-11e8-8c49-080027f6d038   1G         RWO            Delete           Bound     ns-mongo/mongodb-pv-claim-mongod-ss-0   glusterfs-storage             1d

pvc-740db9df-b990-11e8-8c49-080027f6d038   1G         RWO            Delete           Bound     ns-mongo/mongodb-pv-claim-mongod-ss-1   glusterfs-storage             1d

pvc-78ccd2ce-b990-11e8-8c49-080027f6d038   1G         RWO            Delete           Bound     ns-mongo/mongodb-pv-claim-mongod-ss-2   glusterfs-storage             1d

# kubectl get all -n ns-mongo -l role=mongors

No resources found.

* Service(headless)와 Statefulset을 삭제


# ./04-recreate_service.sh 

NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS    CLAIM                                   STORAGECLASS        REASON    AGE

pvc-6eb5c4f7-b990-11e8-8c49-080027f6d038   1G         RWO            Delete           Bound     ns-mongo/mongodb-pv-claim-mongod-ss-0   glusterfs-storage             1d

pvc-740db9df-b990-11e8-8c49-080027f6d038   1G         RWO            Delete           Bound     ns-mongo/mongodb-pv-claim-mongod-ss-1   glusterfs-storage             1d

pvc-78ccd2ce-b990-11e8-8c49-080027f6d038   1G         RWO            Delete           Bound     ns-mongo/mongodb-pv-claim-mongod-ss-2   glusterfs-storage             1d

service "mongodb-svc" unchanged

service "mongodb-hs" created

statefulset "mongod-ss" created

NAME                                                 TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)           AGE

svc/glusterfs-dynamic-mongodb-pv-claim-mongod-ss-0   ClusterIP   10.134.165.156   <none>        1/TCP             1d

svc/glusterfs-dynamic-mongodb-pv-claim-mongod-ss-1   ClusterIP   10.131.121.182   <none>        1/TCP             1d

svc/glusterfs-dynamic-mongodb-pv-claim-mongod-ss-2   ClusterIP   10.138.205.244   <none>        1/TCP             1d

svc/mongodb-hs                                       ClusterIP   None             <none>        27017/TCP         6s

svc/mongodb-svc                                      NodePort    10.129.188.234   <none>        27017:30017/TCP   1d


NAME                     DESIRED   CURRENT   AGE

statefulsets/mongod-ss   3         3         6s


NAME                              READY     STATUS              RESTARTS   AGE

po/mongo-client-799dc789b-6rgvv   1/1       Running             0          1d

po/mongod-ss-0                    1/1       Running             0          6s

po/mongod-ss-1                    1/1       Running             0          3s

po/mongod-ss-2                    0/1       ContainerCreating   0          1s

NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS    CLAIM                                   STORAGECLASS        REASON    AGE

pvc-6eb5c4f7-b990-11e8-8c49-080027f6d038   1G         RWO            Delete           Bound     ns-mongo/mongodb-pv-claim-mongod-ss-0   glusterfs-storage             1d

pvc-740db9df-b990-11e8-8c49-080027f6d038   1G         RWO            Delete           Bound     ns-mongo/mongodb-pv-claim-mongod-ss-1   glusterfs-storage             1d

pvc-78ccd2ce-b990-11e8-8c49-080027f6d038   1G         RWO            Delete           Bound     ns-mongo/mongodb-pv-claim-mongod-ss-2   glusterfs-storage             1d


Keep running the following command until all 'mongod-ss-n' pods are shown as running:  kubectl get svc,sts,pods -n ns-mongo

# kubectl get all -n ns-mongo -l role=mongors

NAME                     DESIRED   CURRENT   AGE

statefulsets/mongod-ss   3         3         3m


NAME             READY     STATUS    RESTARTS   AGE

po/mongod-ss-0   1/1       Running   0          3m

po/mongod-ss-1   1/1       Running   0          3m

po/mongod-ss-2   1/1       Running   0          3m

* Service와 Replicaset 재생성. 새로운 mongod pod가 만들어지면서 기존 pv를 binding



# kubectl exec -it -n $MONGOD_NAMESPACE $MONGO_CLIENT -- mongo mongodb://mongod-ss-0.mongodb-hs.ns-mongo.svc.cluster.local:27017

MongoDB shell version v3.4.2

connecting to: mongodb://mongod-ss-0.mongodb-hs.ns-mongo.svc.cluster.local:27017

MongoDB server version: 3.4.17

MainRepSet:PRIMARY> db.getSiblingDB('admin').auth("main_admin", "abc123");

1

MainRepSet:PRIMARY> use test;

switched to db test

MainRepSet:PRIMARY> db.testcoll.find();

{ "_id" : ObjectId("5b9fd8f0bc9812b50016a157"), "a" : 1 }

{ "_id" : ObjectId("5b9fd8f8bc9812b50016a158"), "b" : 2 }

* 기존 PV의 데이터가 그대로 유지되면서 Replicaset은 초기 상태로 복구(mongod-ss-0이 primary), db 데이터도 그대로 유지



- Barracuda -


블로그 이미지

Barracuda

Bryan의 Tech-Log. 기록은 역사다. 나는 역사를 공유하고 그 안에서 배우며, 또 다른 역사를 써나간다

댓글을 달아 주세요

  • Coramdeo 2018.09.23 13:40  댓글주소  수정/삭제  댓글쓰기

    좋은 정보 감사드립니당^^
    또한 저도 한번 블로그를 운영해보고싶습니다..
    하지만 초대장이 있어야 가입을할수있더군요..
    혹시 초대장을 나눠주실수있으신가요??
    kimsi539816@gmail.com으로 연락주시면 감사하겠습니다.^^ Happy 추석 입니당♥♥

Kubernetes의 컨테이너  오케스트레이션을 이용한 어플리케이션의 구현 방식 또는 기술에서 중요하게 다뤄야 하는 것 중 하나가 StatefulSet 이다. 개념을 명확히 해야 하는 점에서, 아래의 두 가지 개념을 염두에 두고 상황에 따라 잘 활용해야 할 필요가 있어서 본 시리즈를 기획하였다.



Stateless Application

    • Web front-end 와 같이 디스크에 중요한 데이터가 없는 것들
    • 필요한 만큼의 여러 개의 똑같은 컨테이너를 시작/종료할 수 있는 것들
    • Ephemeral 특성 - 컨테이너가 죽으면 내부에 보관중인 데이터는 사라짐
    • Instance 별의 특별한 데이터가 없을 것 

Stateful Application

    • Container-specific 특성(주로 호스트/도메인명, 드물게는 IP 주소)
    • 암호, 인증키, 설정값 등의 Instance 개별 할당
    • 적용가능 Application(Clustered) 들은 다음과 같음
      : MySQL(Galera Cluster) Mongodb(Replicaset), Zookeeper ensemble, PostgreSQL cluster, Redis, ElasticSearch, etc.
   
 





Kubernetes 에서는 Stateful Application의 오케스트레이션을 위해서 StatefulSet(workload API Object, Kubernetes 1.9 버전부터 정식 지원) 형태로 구현되어 있는데,  본 5편으로 이루어진 시리즈에서는 다음의 내용들을 직접 구현하는 방법들을 정리해 두기로 한다. 


여기서 보여지거나 언급되는 각 솔루션들의 구현 형태는 여러 다양한 변형이 존재할 수 있으며, Kubernetes 1.9.x 버전에서 정상 작동하는 실제적 내용으로 수정/검증, 편집된 것이며, 레퍼런스가 있을 경우 아래에 별도 명시한다.


  • Nginx Web Cluster(본 편 ... 1/5)
  • Mongodb Replicaset by StatefulSet(2/5)
  • Mariadb Galera Cluster by StatefulSet and etcd(3/5)
  • Mariadb pxc-cluster(4/5)
  • Zookeeper Ensemble by StatefulSet(5/5)

Nginx Web Cluster 구현

주로 Kubernetes 공식 문서 내용을 기준으로 하여, StatefulSet의 주요 특징들을 확인하는 내용 위주로 정리한다

필요한 사항은 다음과 같다.
  • Running Kubernetes Cluster - v1.9 이상
  • Persistent Storage - GlusterFS(Hyper-converged 또는 External)

다음의 YAML 파일을 이용하여 ns-statefulset 네임스페이스 내에 Nginx 컨테이너 이미지로 StatefulSet 을 생성한다.

# kubectl create namespace ns-statefulset
namespace "ns-statefulset" created

# vi nginx-statefulset-test.yaml
# Service/endpoint for load balancing the client connection from outside
# By NodePort
---
apiVersion: v1
kind: Service
metadata:
  namespace: ns-statefulset
  name: nginx-svc
  labels:
    role: nginx
spec:
  type: NodePort
  ports:
    - port: 80
      name: client
      nodePort: 30080
  selector:
    role: nginxrs

# A headless service to create DNS records
---
apiVersion: v1
kind: Service
metadata:
  namespace: ns-statefulset
  name: nginx-hs
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
    targetPort: 80
  clusterIP: None
  selector:
    role: nginxrs

# StatefulSet for nginx cluster
---
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
  namespace: ns-statefulset
  name: nginx-ss
spec:
  serviceName: nginx-hs
  replicas: 3
  template:
    metadata:
      labels:
        role: nginxrs
        environment: test
    spec:
      containers:
      - name: nginx
        image: gcr.io/google_containers/nginx-slim:0.8
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www-pvc
          mountPath: /usr/share/nginx/html
      terminationGracePeriodSeconds: 10
  volumeClaimTemplates:
  - metadata:
      name: www-pvc
      annotations:
        volume.beta.kubernetes.io/storage-class: glusterfs-storage
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 100Mi

# kubectl create -f nginx-statefulset-test.yaml
service "nginx-svc" created
service "nginx-hs" created
statefulset "nginx-ss" created


# kubectl get pods -n ns-statefulset -w
NAME         READY     STATUS              RESTARTS   AGE
nginx-ss-0   0/1       ContainerCreating   0          39s
nginx-ss-0   1/1       Running   0         1m
nginx-ss-1   0/1       Pending   0         0s
nginx-ss-1   0/1       Pending   0         0s
nginx-ss-1   0/1       ContainerCreating   0         0s
nginx-ss-1   1/1       Running   0         1m
nginx-ss-2   0/1       Pending   0         0s
nginx-ss-2   0/1       Pending   0         0s
nginx-ss-2   0/1       ContainerCreating   0         0s
nginx-ss-2   1/1       Running   0         1m
* 별도 설정을 하지 않으면 각 Pod는 순번(Ordinal Index)에 따라 하나씩 순차적으로 생성됨 

# kubectl get pods -n ns-statefulset 
NAME         READY     STATUS    RESTARTS   AGE
nginx-ss-0   1/1       Running   0          5m
nginx-ss-1   1/1       Running   0          4m
nginx-ss-2   1/1       Running   0          2m

# kubectl get service -n ns-statefulset
NAME                                   TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
glusterfs-dynamic-www-pvc-nginx-ss-0   ClusterIP   10.131.7.158     <none>        1/TCP          10d
glusterfs-dynamic-www-pvc-nginx-ss-1   ClusterIP   10.128.134.234   <none>        1/TCP          10d
glusterfs-dynamic-www-pvc-nginx-ss-2   ClusterIP   10.141.206.45    <none>        1/TCP          10d
nginx-hs                               ClusterIP   None             <none>        80/TCP         10d
nginx-svc                              NodePort    10.137.146.250   <none>        80:30080/TCP   10d

# kubectl get pvc,pv -n ns-statefulset 
NAME                     STATUS    VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS        AGE
pvc/www-pvc-nginx-ss-0   Bound     pvc-a107f13f-57ed-11e8-8c49-080027f6d038   1G         RWO            glusterfs-storage   10d
pvc/www-pvc-nginx-ss-1   Bound     pvc-a5f52c7d-57ed-11e8-8c49-080027f6d038   1G         RWO            glusterfs-storage   10d
pvc/www-pvc-nginx-ss-2   Bound     pvc-aa78d9ba-57ed-11e8-8c49-080027f6d038   1G         RWO            glusterfs-storage   10d

NAME                                          CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS    CLAIM                               STORAGECLASS        REASON    AGE
pv/pvc-a107f13f-57ed-11e8-8c49-080027f6d038   1G         RWO            Delete           Bound     ns-statefulset/www-pvc-nginx-ss-0   glusterfs-storage             10d
pv/pvc-a5f52c7d-57ed-11e8-8c49-080027f6d038   1G         RWO            Delete           Bound     ns-statefulset/www-pvc-nginx-ss-1   glusterfs-storage             10d
pv/pvc-aa78d9ba-57ed-11e8-8c49-080027f6d038   1G         RWO            Delete           Bound     ns-statefulset/www-pvc-nginx-ss-2   glusterfs-storage             10d



StatefulSet 만의 특징적인 사항들의 확인


[Service와 Pod의 네트워크 ID 확인]


Busybox pod 를 통해 StatefulSet의 Headless Service(nginx-hs)와 Pod의 DNS 정보를 확인

# kubectl run -n ns-statefulset -it busybox --image=busybox

/ # nslookup -type=a nginx-hs.ns-statefulset.svc.cluster.local

Server: 10.128.0.10

Address: 10.128.0.10:53


Name: nginx-hs.ns-statefulset.svc.cluster.local

Address: 10.40.0.4

Name: nginx-hs.ns-statefulset.svc.cluster.local

Address: 10.32.0.12

Name: nginx-hs.ns-statefulset.svc.cluster.local

Address: 10.38.0.4


/ # nslookup -type=a nginx-ss-0.nginx-hs.ns-statefulset.svc.cluster.local

Server: 10.128.0.10

Address: 10.128.0.10:53


Name: nginx-ss-0.nginx-hs.ns-statefulset.svc.cluster.local

Address: 10.40.0.4

* Headless service는 StatefulSet의 도메인을 구성하는 단위이며, ${서비스명}.${네임스페이스}.svc.cluster.local 형식의 도메인이 만들어 짐

* 위의 yaml 파일 내용을 보면 headless service의 spec: 에서 clusterIp: None 으로 지정되어 있는 것이, 일반적인 kubernetes service와 극명하게 차이가 나는 부분이며, 이를 통해 headless service와 연결되는 특정한 Pod에 직접 access 가 가능하게 됨 

* 각 Pod의 이름은 ${StatefulSet 이름}-${순번} 형식으로 결정되며,Pod의 도메인 형식은 ${StatefulSet 이름}-${순번}.${서비스명}.${네임스페이스}.svc.cluster.local 이 됨



[각 Pod의 고유 정보를 설정하고 Scale-out, Self-healing 확인]


# for i in $(seq 0 2); \

  do kubectl exec nginx-ss-$i -n ns-statefulset \

    -- sh -c 'echo $(hostname -f) > /usr/share/nginx/html/index.html'; \

  done


# for i in $(seq 0 2); \

  do kubectl exec nginx-ss-$i -n ns-statefulset \

    -- sh -c 'curl -s localhost'; \

  done 

nginx-ss-0.nginx-hs.ns-statefulset.svc.cluster.local

nginx-ss-1.nginx-hs.ns-statefulset.svc.cluster.local

nginx-ss-2.nginx-hs.ns-statefulset.svc.cluster.local

# for i in $(seq 0 2); \

  do kubectl exec nginx-ss-$i -n ns-statefulset \

    -- sh -c 'cat /usr/share/nginx/html/index.html'; \

  done 

nginx-ss-0.nginx-hs.ns-statefulset.svc.cluster.local

nginx-ss-1.nginx-hs.ns-statefulset.svc.cluster.local

nginx-ss-2.nginx-hs.ns-statefulset.svc.cluster.local


# kubectl scale -n ns-statefulset statefulset nginx-ss --replicas=4

statefulset "nginx-ss" scaled


# kubectl get pods -n ns-statefulset -w

NAME                       READY     STATUS    RESTARTS   AGE

busybox-5f9469d8dc-cwjhf   1/1       Running   1          7h

nginx-ss-0                 1/1       Running   0          7h

nginx-ss-1                 1/1       Running   0          7h

nginx-ss-2                 1/1       Running   0          7h

nginx-ss-3                 1/1       Running   0          8m


# kubectl exec nginx-ss-3 -n ns-statefulset \

  -- sh -c 'echo $(hostname -f) > /usr/share/nginx/html/index.html'

* StatefulSet 의 replicas 를 4로 증가하고 nginx 의 index.html 생성


# curl -s http://10.255.10.170:30080

nginx-ss-3.nginx-hs.ns-statefulset.svc.cluster.local

# curl -s http://10.255.10.170:30080

nginx-ss-2.nginx-hs.ns-statefulset.svc.cluster.local

...

# curl -s http://10.255.10.170:30080

nginx-ss-1.nginx-hs.ns-statefulset.svc.cluster.local

...

# curl -s http://10.255.10.170:30080

nginx-ss-0.nginx-hs.ns-statefulset.svc.cluster.local

* 일반 service인 Nginx-svc 서비스(NodePort 30080으로 expose 됨)로 curl 접속, 각 Pod로 접속이 분배(로드밸런싱) 됨을 확인


# kubectl delete -n ns-statefulset pod nginx-ss-1

pod "nginx-ss-1" deleted


# kubectl get pods -n ns-statefulset -wNAME                       READY     STATUS    RESTARTS   AGE

busybox-5f9469d8dc-cwjhf   1/1       Running   1          7h

nginx-ss-0                 1/1       Running   0          7h

nginx-ss-1                 1/1       Running   0          5s

nginx-ss-2                 1/1       Running   0          7h

nginx-ss-3                 1/1       Running   0          16m


# kubectl exec -it -n ns-statefulset \

  busybox-5f9469d8dc-fv4jw \

  -- sh -c 'wget -qO- nginx-ss-1.nginx-hs.ns-statefulset.svc.cluster.local'

nginx-ss-1.nginx-hs.ns-statefulset.svc.cluster.local

* Pod nginx-ss-1를 삭제하였으나 self-healing이 동작하여 StatefulSet 의 향상은 유지되어, pod nginx-ss-1 에 원래의 Persistent Volume이 연결됨(해당 Pod 고유의 기존 데이터도 그대로 유지)

* Busybox 에서 pod nginx-ss-1의 웹서버에 wget으로 접속, 웹서비스가 정상 동작됨을 확인



[StatefulSet과 관여되는 서비스 Clear, Persistent Volume Clear]


# kubectl delete service -n ns-statefulset nginx-svc 

service "nginx-svc" deleted

# kubectl delete service -n ns-statefulset nginx-hs

service "nginx-hs" deleted

# kubectl delete statefulset -n ns-statefulset nginx-ss

statefulset "nginx-ss" deleted


# kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS    CLAIM                               STORAGECLASS        REASON    AGE
pvc-978bac5f-ae99-11e8-8c49-080027f6d038   1G         RWO            Delete           Bound     ns-statefulset/www-pvc-nginx-ss-0   glusterfs-storage             7h
pvc-9c62652a-ae99-11e8-8c49-080027f6d038   1G         RWO            Delete           Bound     ns-statefulset/www-pvc-nginx-ss-1   glusterfs-storage             7h
pvc-a0b1695a-ae99-11e8-8c49-080027f6d038   1G         RWO            Delete           Bound     ns-statefulset/www-pvc-nginx-ss-2   glusterfs-storage             7h
pvc-cef812d4-aed3-11e8-8c49-080027f6d038   1G         RWO            Delete           Bound     ns-statefulset/www-pvc-nginx-ss-3   glusterfs-storage             50m

# for i in $(seq 0 3); do kubectl delete pvc -n ns-statefulset www-pvc-nginx-ss-$i; done
persistentvolumeclaim "www-pvc-nginx-ss-0" deleted
persistentvolumeclaim "www-pvc-nginx-ss-1" deleted
persistentvolumeclaim "www-pvc-nginx-ss-2" deleted
persistentvolumeclaim "www-pvc-nginx-ss-3" deleted

* StatefulSet에 연결된 Persistent Volume이 삭제되지 않을 경우, 확인 후 명시적으로 Clear 해 주어야 함. 이는 scale-in을 하여 Pod가 삭제되는 경우에도 동일하게 발생할 수 있으며, 글을 쓰기 시작한 시점에는 이러한 문제를 해결하고자 별도의 노력이 필요한 상황이며, 아래 2번째 레퍼런스를 참고해 볼 필요가 있음.



[References]

  • https://kubernetes.io/docs/tutorials/stateful-application/basic-stateful-set/
  • https://medium.com/@marko.luksa/graceful-scaledown-of-stateful-apps-in-kubernetes-2205fc556ba9



- Barracuda -

 

블로그 이미지

Barracuda

Bryan의 Tech-Log. 기록은 역사다. 나는 역사를 공유하고 그 안에서 배우며, 또 다른 역사를 써나간다

댓글을 달아 주세요




개요


GitLab 을 위한 Custom Hook은 다음의 3 가지 중 하나로 구현된다(Community Edition 기준).


  • pre-receive: Git 서버가 클라이언트로부터 Push 요청을 받은 즉시 수행되며, 스크립트에서 non-zero 값을 return 하면 Push 요청은 reject 된다. Push 요청에 대한 값은 스크립트 내에서 stdin 스트림 값을 읽어서 사용 가능하다
  • update: pre-receive 와 유사하지만, pre-receive 는 한 번의 Push에 대해 단 한 번 수행되며, update는 각각의 Branch 마다 triggering 되는 점이 다르다. 따라서 여러 Branch에 Push 를 수행하게 되면 특정한 브랜치에 대해서만 reject 되게 처리되게 하고 싶을 떄 사용한다
  • post-receive: Push 에 대한 모든 처리가 완료된 직후에 수행된다. Push 데이터에 대해 stdin 을 참조해서 사용하면 되며,  주로 사용자에게 메일 발송이나 CI 서버로의 triggering 또는 이슈 트래킹 시스템으로 티켓을 업데이트할 때 사용한다


스크립트 pre-receive 구현 및 활용 방법


  • pre-receive-commit-msg-check.py 파일 최종 버전을 GitLab 서버의 /root/custom_hooks_src에 저장해 둔다(백업/확인용, option 사항)
  • pre-receive-commit-msg-check.py 파일 최종 버전을 /root/custom_hooks 에 pre-receive 라는 이름으로 저장하고, chmod a+x pre-receive, chown git.git pre-receive 로 설정한다
  • copy_custom_hooks.sh 스크립트의 target_path_list 에 pre-receive Hook 을 적용할 repository 정보를 등록한다
  • copy_custom_hooks.sh 파일을 적당한 위치에 저장(/root/copy_custom_hooks.sh)해 두고 실행하면 지정된 repository 의 해당 path 로 복사된다(copy_custom_hooks.sh 파일의 내용은 다음과 같다)



  • 본 pre-receive 스크립트의 commit 제약 조건은 다음과 같다

Commit message에 issue-123 또는 issue#123 또는 issue-#123 또는 hotfix 또는 force 가 없으면

Remote 에 Push 시에 오류 발생함(대소문자 구분 없음)


  • pre-receive 의 원본에 해당되는 pre-receive-commit-msg-check.py 스크립트의 내용은 다음고 같으며, GitHub 의 여기에 공개되어 있다




- Barracuda -





블로그 이미지

Barracuda

Bryan의 Tech-Log. 기록은 역사다. 나는 역사를 공유하고 그 안에서 배우며, 또 다른 역사를 써나간다

댓글을 달아 주세요


전편에 이어지는 내용으로, 이번에는 예제 프로젝트인 hugo-app 을 대상으로 개발~배포~실행까지 Pipeline을 구성하여 CI/CD 과정을 구현해 보자


Jenkins-leader 서비스 기동


  • 새로운 프로젝트를 시작할 때, 전용의 Jenkins 빌더 셋을 만드는 과정에 해당
  • 기존에 제작/테스트 했던 jenkins 서비스 환경을 tear-down 하고 Custom Jenkins 빌더로 새로 시작

[root@kubemaster 00-jenkins-custom-image]# kubectl delete -f 02-jenkins-dep-svc.yaml -n ns-jenkins

[root@kubemaster 00-jenkins-custom-image]# kubectl delete -f 01-jenkins-leader-pvc.yaml -n ns-jenkins

[root@kubemaster 00-jenkins-custom-image]# kubectl delete -f 00-jenkins-sa-clusteradmin-rbac.yaml


  • 새로운 시작

[root@kubemaster ~]# mkdir jenkins-custom-k8s-cicd/01-jenkins-custom-deploy

[root@kubemaster ~]# cd jenkins-custom-k8s-cicd/01-jenkins-custom-deploy

[root@kubemaster 01-jenkins-custom-deploy]# cp ../00-jenkins-custom-image/0*.yaml .


[root@kubemaster 01-jenkins-custom-deploy]# export JENKINS_LEADER_IMAGE="drlee001/jenkins-leader:2.60.3-ns-version"

[root@kubemaster 01-jenkins-custom-deploy]# sed -i -e "s|image: .*|image: ${JENKINS_LEADER_IMAGE}|g" 02-jenkins-dep-svc.yaml

[root@kubemaster 01-jenkins-custom-deploy]# kubectl create -f 00-jenkins-sa-clusteradmin-rbac.yaml

[root@kubemaster 01-jenkins-custom-deploy]# kubectl create -f 01-jenkins-leader-pvc.yaml -n ns-jenkins

[root@kubemaster 01-jenkins-custom-deploy]# kubectl create -f 02-jenkins-dep-svc.yaml -n ns-jenkins

* Docker Hub 에 Push된 Custom Jenkins 빌더 이미지 Deploy


GitHub 프로젝트 연동을 위한 Jenkins 설정


앞선 1/2 의 내용대로 jenkins-leader 이미지를 생성하였다면 admin / admin 으로 Jenkins console 에서 로그인할 수 있을 것이다. Manage Jenkins > Configure System 선택


GitHub : GitHub Server > Add GitHub Server - GitHub Server 클릭


아래로 펼쳐진 항목들 중 두 번 째 Advanced 버튼 클릭


Additional Actions: Manage additional GitHub actions - Convert login and password to token 클릭 


아래로 펼쳐지는 항목들 중 From login and password 선택 > 본인의 GitHub 로그인 계정 입력 > Create token credentials 클릭 > Created ... 성공 메시지가 나오면 하단 Save 클릭


* 주의: Create token credential 단계에서 "GH token 생성 오류" 발생시, 이미 plugin 을 위한 토큰이 생성된 경우이므로, github account > Settings > Developer settings > Personal access tokens 의 "Jenkins Github Plugin token" 을 삭제해 주어야 한다


메인 메뉴 Manage Jenkins > Configure System > GitHub: GitHub Server - Credentials > None 클릭 > GitHub (https://api...) 항목 클릭 > 아래 두 번 째 Advanced 버튼 클릭


아래 펼쳐지는 항목들 중 Shared secret: none 클릭 > GitHub (https://api...) 항목 클릭 > 하단 Save 버튼 클릭 




대상 프로젝트 Pull(Clone), Build & Deploy


  • 아래 예제로 제공되는 간단한 Static Web 프로젝트를 대상으로 CI/CD 과정을 수행해 보자(이 과정을 응용하면 일반적인 다른 프로젝트들도 역시 연동이 가능하게 될 것이다). 실제 테스트를 위해 GitHub의 아래 프로젝트로 이동하여 본인의 GitHub 계정으로 fork 하고 GITHUB_ACCOUNT 변수에 본인의 Account 를 할당해서 사용할 것을 추천한다. 
[root@kubemaster jenkins-custom-k8s-cicd]# export GITHUB_ACCOUNT='YourGithubAccount'
[root@kubemaster jenkins-custom-k8s-cicd]# git clone https://github.com/${GITHUB_ACCOUNT}/test-webapp.git


[참고] 여기까지 수행하였다면, 본 CI/CD 시리즈의 전체 디렉토리 구조는 다음과 같이 나타난다 

[root@kubemaster jenkins-custom-k8s-cicd]# ls -l

total 4

drwxr-xr-x. 2 root root 115  4월  9 00:32 00-jenkins-custom-image

drwxr-xr-x. 2 root root 115  4월  9 00:33 01-jenkins-custom-deploy

drwxr-xr-x. 3 root root  39  4월  9 12:17 docker

-rw-r--r--. 1 root root 376 10월 24 17:35 README.md

drwxr-xr-x. 4 root root 148  4월  9 13:52 test-webapp


  • Clone 받은 프로젝트(또는 본인의 연동 대상 프로젝트)로 이동하고 파일 목록을 확인해 보자. 실제 상황에서 빌드 대상이 되는 파일들은 applications 내의 하위 폴더로 적절히 위치시키면 된다.
[root@kubemaster jenkins-custom-k8s-cicd]# cd test-webapp/
[root@kubemaster test-webapp]# ls -l
total 20
drwxr-xr-x. 3 root root   27  4월  9 12:23 applications
-rwxr-xr-x. 1 root root  192  4월  9 13:18 build-and-push.sh
-rwxr-xr-x. 1 root root  626  4월  9 13:52 github-repo-set.sh
-rw-r--r--. 1 root root 1946  4월  9 13:44 Jenkinsfile
-rw-r--r--. 1 root root   14  4월  9 13:52 README.md
-rwxr-xr-x. 1 root root  104  4월  9 13:24 run-and-show.sh

  • 실제 Build 대상이 되는 프로그램의 리소스들은 다음과 같다. 실제로도 유사한 형태로 구성하면 된다.
[root@kubemaster test-webapp]# ls -l applications/test-webapp-1/
total 48
-rw-r--r--. 1 root root   139  4월  9 12:14 Dockerfile
-rw-r--r--. 1 root root 38651  4월  9 12:34 DockerFileEx.jpg
-rw-r--r--. 1 root root   396  4월  9 16:51 index.html
drwxr-xr-x. 2 root root    29  4월  9 13:22 k8s

[root@kubemaster test-webapp]# ls -l applications/test-webapp-1/k8s/
total 4
-rw-r--r--. 1 root root 615  4월  9 13:22 deployment.yaml
* k8s 디렉토리 내에는 test-webapp-1 프로그램의 Kubernetes 내 Deploy 되는 형태를 정의하는 Yaml 파일을 배치한다. 내용은 아래와 같다.

[root@kubemaster test-webapp]#  cat  applications/test-webapp-1/k8s/deployment.yaml 
apiVersion: v1
kind: Service
metadata:
  name: test-webapp-1
  labels:
    app: test-webapp
spec:
  ports:
  - port: 80
    targetPort: 80
  selector:
    app: test-webapp
    tier: test
  type: NodePort

---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: test-webapp-1
  labels:
    app: test-webapp
spec:
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: test-webapp
        tier: test
    spec:
      containers:
      - image: YourDockerHubAccount/test-webapp-1:latest
        name: test-webapp-1
        ports:
        - containerPort: 80
          name: test-webapp-1


대상 프로그램의 Containerizing

  • 다음 스크립트 내용을 수행하여 대상 프로그램을 Docker Image 로 만들고 Docker Hub 에 Push
[root@kubemaster test-webapp]# cat build-and-push.sh 
#!/bin/sh
DOCKERHUB_ACCOUNT="YourDockerHubAccount"
docker login -u ${DOCKERHUB_ACCOUNT}
docker build -t ${DOCKERHUB_ACCOUNT}/test-webapp-1:latest -f applications/test-webapp-1/Dockerfile applications/test-webapp-1/
docker push ${DOCKERHUB_ACCOUNT}/test-webapp-1:latest
[root@kubemaster test-webapp]# ./build-and-push.sh
Password: ********
Login Succeeded
Sending build context to Docker daemon 44.54 kB
Step 1/4 : FROM nginx:latest
 ---> c5c4e8fa2cf7
Step 2/4 : COPY index.html /usr/share/nginx/html/index.html
 ---> Using cache
 ---> 8e420f1a99bc
Step 3/4 : COPY DockerFileEx.jpg /usr/share/nginx/html/DockerFileEx.jpg
 ---> Using cache
 ---> b39fed81c0e4
Step 4/4 : EXPOSE 80
 ---> Using cache
 ---> 818b28961124
Successfully built 818b28961124
The push refers to a repository [docker.io/YourDockerHubAccount/test-webapp-1]
300a550bf23d: Pushed 
a3cb71492d49: Pushed 
... 
latest: digest: sha256:a44e5bb0fe8af44630e24c16d49f6bb34cf5f7a0b66b5e30de20fc590273396b size: 1364
* DOCKERHUB_ACCOUNT 변수 값에는 본인 자신의 Ducker Hub 계정을 사용

[root@kubemaster test-webapp]# docker rmi YourDockerHubAccount/test-webapp-1:latest
* 실제 Docker Image 가 Pull 되어 실행되는 머신은 현재 작업중인 Master 노드가 아니므로 불필요한 이미지 삭제

  • 다음 스크립트를 수행하여 Kubernetes 내에 대상 프로그램의 Service/Pod 를 기동
[root@kubemaster test-webapp]# cat run-and-show.sh 
#!/bin/sh
kubectl apply -f applications/test-webapp-1/k8s/deployment.yaml
kubectl get svc test-webapp-1

[root@kubemaster test-webapp]# ./run-and-show.sh 
service "test-webapp-1" created
deployment "test-webapp-1" created
NAME            TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
test-webapp-1   NodePort   10.128.227.169   <none>        80:32562/TCP   21h

  • 웹브라우저를 사용하여 대상 프로그램(Webapp)에 접속, 확인해 보자


'Docker Hub로의 Push' 와 'Kubectl 실행'을 위한 Token 생성 

  • Jenkins-leader 에서 등록한 작업(item)은 jenkins-slave Pod가 동적으로 생성되면서 그 내부에서 처리되고, 완료 후 해당 Pod는 자동으로 종료된다.
  • Jenkinsfile의 container 영역에서 정의한 작업이 수행되는 과정에서, Docker Hub 쪽으로 컨테이너 이미지 Push, Kubernetes Cluster의 deploy된 Pod/container 이미지를 교체할 때 각각, 인증을 위한 token 이 Kubernetes 내에 secret 형태로 존재해야 한다.
  • DOCKER_ACCOUNT 변수에 본인의 Docker Hub 계정을 사용한다.

# export DOCKER_ACCOUNT="drlee001"

# docker login -u $DOCKER_ACCOUNT

password:

Login Succeeded

#  kubectl create secret -n ns-jenkins generic docker-config --from-file=$HOME/.docker/config.json

secret "docker-config" created

# kubectl create secret -n ns-jenkins generic kube-config --from-file=$HOME/.kube/config

secret "kube-config" created

# kubectl get secret -n ns-jenkins 

NAME                  TYPE                                  DATA      AGE

default-token-chxlp   kubernetes.io/service-account-token   3         3d

docker-config         Opaque                                1         2m

jenkins-token-dhx8s   kubernetes.io/service-account-token   3         3d

kube-config           Opaque                                1         2m



  • GitHub으로 연동되는 빌드 대상 프로젝트 repository 의 Jenkinsfile 내용은 다음과 같다. 여기서 정의한 Pod/container 구성에 맞춰진 jenkins-slave Pod가 동적으로 수행된다.
  • 아래 구성대로 기동되는 Pod는 내부에 총 3개의컨테이너를 포함한다(docker, kubectl 그리고 jnlp 컨테이너).
#!groovy
podTemplate(label: 'test-webapp-1', containers: [
    containerTemplate(name: 'kubectl', image: 'smesch/kubectl', ttyEnabled: true, command: 'cat',
        volumes: [secretVolume(secretName: 'kube-config', namespace: 'ns-jenkins', mountPath: '/root/.kube')]),
    containerTemplate(name: 'docker', image: 'docker', ttyEnabled: true, command: 'cat',
        envVars: [containerEnvVar(key: 'DOCKER_CONFIG', value: '/tmp/'),])],
        volumes: [secretVolume(secretName: 'docker-config', namespace: 'ns-jenkins', mountPath: '/tmp'),
                  hostPathVolume(hostPath: '/var/run/docker.sock', mountPath: '/var/run/docker.sock')
  ]) {

    node('test-webapp-1') {

        def DOCKER_HUB_ACCOUNT = 'YourDockerHubAccount'
        def DOCKER_IMAGE_NAME = 'test-webapp-1'
        def K8S_DEPLOYMENT_NAME = 'test-webapp-1'
        def POD_NAMESPACE = 'default'

        stage('Clone test-webapp-1 App Repository') {
            checkout scm
            
            container('docker') {
                stage('Docker Build & Push Current & Latest Versions') {
                    sh ("docker build -t ${DOCKER_HUB_ACCOUNT}/${DOCKER_IMAGE_NAME}:${env.BUILD_NUMBER} -f applications/test-webapp-1/Dockerfile applications/test-webapp-1/")
                    sh ("docker push ${DOCKER_HUB_ACCOUNT}/${DOCKER_IMAGE_NAME}:${env.BUILD_NUMBER}")
                    sh ("docker tag ${DOCKER_HUB_ACCOUNT}/${DOCKER_IMAGE_NAME}:${env.BUILD_NUMBER} ${DOCKER_HUB_ACCOUNT}/${DOCKER_IMAGE_NAME}:latest")
                    sh ("docker push ${DOCKER_HUB_ACCOUNT}/${DOCKER_IMAGE_NAME}:latest")
                }
            }

            container('kubectl') {
                stage('Deploy New Build To Kubernetes') {
                    sh ("kubectl set image -n ${POD_NAMESPACE} deployment/${K8S_DEPLOYMENT_NAME} ${K8S_DEPLOYMENT_NAME}=${DOCKER_HUB_ACCOUNT}/${DOCKER_IMAGE_NAME}:${env.BUILD_NUMBER}")
                }
            }

        }        
    }
}
* jenkins-slave의 namespace가 jenkins-leader Pod와 동일하게 ns-jenkins 임에 유의



Jenkins pipeline 작업을 통한 자동 빌드 & 배포 실행


  • 대상 프로그램의 리소스 중 일부를 수정해서 Jenkins pipeline 작업을 수행해 보자

[root@kubemaster test-webapp]# vi applications/test-webapp-1/index.html 

<html>
<head>
</head>
<body>
<p>
  <h2 style="font-family:sans-serif">Hello from K8s-Jenkins pipeline! You've successfully built and run the Test-Webapp-1 app.</h2>
  <br>Version: v04
</p>
<p style="font-family:sans-serif">
  This app is a simple static web page running on <a href="https://hub.docker.com/_/nginx/">nginx base image</a>.
</p>
<p>
  <img src="DockerFileEx.jpg">
</p>
</body>
</html>

[root@kubemaster test-webapp]# git add .

[root@kubemaster test-webapp]# git commit -a -m "Update - index.html"

[master fce13d5] Update - index.html

 2 files changed, 5 insertions(+), 4 deletions(-)

[root@kubemaster test-webapp]# git push origin master

Username for 'https://github.com': YourGitHubAccount

Password for 'https://YourGitHubAccount@github.com': YourGitHubPassword

Counting objects: 11, done.

Delta compression using up to 2 threads.

Compressing objects: 100% (5/5), done.

Writing objects: 100% (6/6), 609 bytes | 0 bytes/s, done.

Total 6 (delta 3), reused 0 (delta 0)

remote: Resolving deltas: 100% (3/3), completed with 3 local objects.

To https://github.com/YourGitHubAccount/test-webapp.git

   24c9645..fce13d5  master -> master

* 프로그램 소스 일부를 수정하고 Commit & Push 한다


Jenkins-leader UI 화면에서 새로운 Item 을 등록한다(이후의 과정은 포스팅 시리즈 1/2(http://bryan.wiki/295 - 빌드테스트 섹션)과 동일하므로 별도 설명 생략)






Console Output 의 마지막에 Finished: SUCCESS 메시지까지 나타나면 성공적으로 빌드 & 배포가 완료된 것이다. 이제 위의 Webapp 접속 화면을 확인하면 수정/변경된 내용을 확인할 수 있을 것이다.


- Barracuda -


[관련 글]

2018/03/20 - [Technical/Cloud, 가상화, PaaS] - [Kubernetes - CI/CD] Customized Jenkins 제작과 활용 - 1/2



블로그 이미지

Barracuda

Bryan의 Tech-Log. 기록은 역사다. 나는 역사를 공유하고 그 안에서 배우며, 또 다른 역사를 써나간다

댓글을 달아 주세요


Kubernetes 를 활용하여 CI/CD를 구현하는 방법은 여러 가지가 있다. 이번 시리즈는 커스텀 Jenkins 이미지를 사용한 컨테이너 Application 빌드 배포 자동화를 구현해 보고자 한다. 본 글은 그 첫 번 째로, 커스텀 Jenkins 이미지를 제작, 테스트 빌드를 통해 CI/CD가 정상적으로 작동하는지를 확인하는 내용이다.


Prerequisites

  • Kubernetes 1.8~1.9.x 가 설치되고 정상 작동 할 것
  • Application 소스 저장소로 사용할 github 계정 준비
  • Persistent Volume으로 사용할 Storage는 Heketi-API로 연동된 Glusterfs(http://bryan.wiki/286 참조)
  • Jenkins 빌드 작업을 위한 네임스페이스틑 ns-jenkins 로 설정하여 사용(별도 지정하지 않으면 default 네임스페이스 사용)
  • 작업 디렉터리는 $HOME/jenkins-custom-k8s-cicd 로 하고, 하위에 각 상황과 용도에 맞는 서브 디렉터리를 만들어 사용


Jenkins 기본 이미지로 Jenkins Pod/Service 기동


[root@kubemaster ~]# kubectl create namespace ns-jenkins

[root@kubemaster ~]# mkdir 00-jenkins-custom-image && cd 00-jenkins-custom-image

[root@kubemaster 00-jenkins-custom-image]# vi 00-jenkins-sa-clusteradmin-rbac.yaml

---
apiVersion: v1
kind: ServiceAccount
metadata:
  namespace: ns-jenkins
  name: jenkins
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: cluster-admin-clusterrolebinding
subjects:
- kind: ServiceAccount
  name: jenkins
  namespace: ns-jenkins
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: cluster-admin-clusterrolebinding-2
subjects:
- kind: ServiceAccount
  name: default
  namespace: ns-jenkins
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin

[root@kubemaster 00-jenkins-custom-image]# vi 01-jenkins-leader-pvc.yaml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: jenkins-leader-pvc
  annotations:
    volume.beta.kubernetes.io/storage-class: glusterfs-storage
  labels:
    app: jenkins-leader
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 500Mi


[root@kubemaster 00-jenkins-custom-image]# vi 02-jenkins-dep-svc.yaml

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: jenkins-leader
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: jenkins-leader
    spec:
      serviceAccountName: jenkins
      securityContext:
        # Jenkins uid:gid=1000:1000
        fsGroup: 1000
      containers:
        - name: jenkins-leader
          image: jenkins
          volumeMounts:
          - name: jenkins-home
            mountPath: /var/jenkins_home
          ports:
          - containerPort: 8080
          - containerPort: 50000
      volumes:
      - name: jenkins-home
        persistentVolumeClaim:
          claimName: jenkins-leader-pvc
---
apiVersion: v1
kind: Service
metadata:
  name: jenkins-leader-svc
  labels:
    app: jenkins-leader
spec:
  type: NodePort
  ports:
  - port: 80
    targetPort: 8080
    protocol: TCP
    name: http
    nodePort: 30500
  - port: 50000
    protocol: TCP
    name: slave
    nodePort: 30501
  selector:
    app: jenkins-leader


[root@kubemaster 00-jenkins-custom-image]# kubectl create -f 00-jenkins-sa-clusteradmin-rbac.yaml 

serviceaccount "jenkins" created

clusterrolebinding "cluster-admin-clusterrolebinding" created


[root@kubemaster 00-jenkins-custom-image]# kubectl create -f 00-jenkins-leader-pvc.yaml -n ns-jenkins 

persistentvolumeclaim "jenkins-leader-pvc" created


[root@kubemaster 00-jenkins-custom-image]# kubectl create -f 01-jenkins-dep-svc.yaml -n ns-jenkins 

deployment "jenkins-leader" created

service "jenkins-leader-svc" created


[root@kubemaster 00-jenkins-custom-image]# kubectl get pods -n ns-jenkins 

NAME                              READY     STATUS    RESTARTS   AGE

jenkins-leader-75869666cc-9kq6q   1/1       Running   0          7m


[root@kubemaster 00-jenkins-custom-image]# kubectl logs -f -n ns-jenkins jenkins-leader-75869666cc-9kq6q

Running from: /usr/share/jenkins/jenkins.war

webroot: EnvVars.masterEnvVars.get("JENKINS_HOME")

...

*************************************************************

Jenkins initial setup is required. An admin user has been created and a password generated.

Please use the following password to proceed to installation:

1541910096194795a11f9b2342b24b8d

This may also be found at: /var/jenkins_home/secrets/initialAdminPassword

*************************************************************

...

Mar 20, 2018 8:35:10 AM hudson.model.AsyncPeriodicWork$1 run

INFO: Finished Download metadata. 60,791 ms

...

* RBAC 설정에서는 각종 권한 처리를 단순하게 하기 위해 ns-jenkins 네임스페이스의 ServiceAccount인 jenkins와 default cluster-admin 권한을 부여한다. 실제 서비스 환경에서는 좀더 세밀한 권한 관리에 신경을 써둘 필요가 생길 수도 있다. 

* 네임스페이스를 따로 지정하지 않으면 모든 jenkins 요소들이 default 네임스페이스에 생성된다

* 설치용 임시암호를 복사해 둔다

* [Tip] 해당 Jenkins-leader container 의 초기 암호 값을 바로 알아 내려면 아래 명령을 실행해도 된다

# kubectl exec -it -n ns-jenkins `kubectl get pods -n ns-jenkins --selector=app=jenkins-leader --output=jsonpath={.items..metadata.name}` -- cat /root/.jenkins/secrets/initialAdminPassword


Jenkins 플러그인 및 커스텀 설정


  • 앞 단계에서 Kubernetes 서비스를 NodePort 로 expose 한 결과를 확인


[root@kubemaster 00-jenkins-custom-image]# kubectl get svc -n ns-jenkins

NAME                                   TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                        AGE

glusterfs-dynamic-jenkins-leader-pvc   ClusterIP   10.139.137.127   <none>        1/TCP                          3d

jenkins-leader-svc                     NodePort    10.141.255.17    <none>        80:30500/TCP,50000:30501/TCP   3d



  • Jenkins-leader 서비스를 실행한 yaml 파일에서 정의한 대로, 웹브라우저로 Minion(Worker Node) IP의 30500 포트로 접속하여, 앞 단계에서 확인한 설치용 임시 암호를 입력한 후 최초 설정 진행

http://10.255.10.171:30050 으로 접속, 설치용 임시 암호 입력(편의상 다음에 나타나는 관리자용 계정/암호는 admin/admin 으로 설정)


Install suggested plugins 를 선택하여 기본 설치 플러그인을 설치


자동으로 기본 Plugin 들이 설치되며, 이후에 Jenkins 관리자 계정과 암호를 등록하고 완료(Start using Jenkins)하면 Jenkins 관리 Console 로 접속


Manage Jenkins > Configure Global Security 선택


TCP port for JNLP - Fixed 50000, Prevent Cross Site ... - 체크 해제, Apply & Save



  • Kubernetes Plugin 설치


Manage Jenkins > Manage Plugins 선택


Available 탭 선택


Cloud Providers 항목으로 스크롤(검색), kubernetes 체크 - Install without restart 클릭 - 설치 후 Go back to top page 선택


  • Kubernetes 빌드를 위한 plugin 설정

Manage Jenkins > Configure System 선택


#  of executors 항목의 값을  0  으로 설정


페이지 맨 끝, Cloud 항목으로 이동, Add a new cloud 선택 > kubernetes 선택



이번 단계에서는 아래 각 항목에 대해 시스템 구성에 맞게 정확히 입력/설정한다

  • Name: kubernetes

  • Kubernetes URL: https://kubernetes.default.svc.cluster.local

  • Disable https certificate check: Yes

  • Kubernetes Namespace: ns-jenkins(Jenkins 서비스를 기동시키는 네임스페이스명)

  • Jenkins URL: http://jenkins-leader-svc.ns-jenkins.svc.cluster.local

  • Jenkins tunnel: jenkins-leader-svc.ns-jenkins.svc.cluster.local:50000

각 항목 입력 후 Credentials: Add - Jenkins 클릭


Credentials 팝업창, Kind: kubernetes service account 선택 > Add 클릭


Credentials: 좌측 None 클릭 > 새로 만들어진 Secret text 선택 > Test Connection > Save 클릭



  • Kubernetes 빌드 테스트


좌측 메인 메뉴의 New Item 클릭 > Item name 입력 > Pipeline 클릭 > 하단 OK 클릭


아래로 스크롤, Script 입력 창에 아래 스크립트 내용 Copy & Paste > Save


podTemplate(label: 'pod-golang', 
    containers: [
        containerTemplate(
            name: 'golang',
            image: 'golang',
            ttyEnabled: true,
            command: 'cat'
        )
    ]
) {
    node ('pod-golang') {

        stage 'Switch to Utility Container'
        container('golang') {

          sh ("go version")

        }
    }
}  


메인 메뉴 Build Now 클릭 > Build History 의 Build 아이템 우측 역삼각형(▼) 클릭 > Console Output 클릭


빌드 과정 및 결과 확인(실제로 빌드 작업이 수행되는 jenkins-slave pod는 ns-jenkins 네임스페이스에서 실행)


빌드가 수행되는 동안 ns-jenkins 네임스페이스의 pod 목록 변화를 확인


  • 최종적으로 Jenkins를 통한 Build test를 위한 golang pod의 lifecycle은 다음 명령어로 확인 가능

# kubectl get pods -n ns-jenkins -w
NAME                              READY     STATUS    RESTARTS   AGE
jenkins-leader-6dc657fb9f-ngsj9   1/1       Running   0          6h
jenkins-slave-jqgwz-5kql9         2/2       Running   0          4m
jenkins-slave-jqgwz-5kql9   2/2       Terminating   0         4m
jenkins-slave-jqgwz-5kql9   0/2       Terminating   0         5m
jenkins-slave-jqgwz-5kql9   0/2       Terminating   0         6m
jenkins-slave-jqgwz-5kql9   0/2       Terminating   0         6m



커스텀 설정된 Jenkins 빌더 이미지 저장


  • 기본 및 kubernetes 플러그인의 설치/설정이 완료된 Jenkins 빌더(leader) 컨테이너의 설정 정보를 추출하고 불필요한 파일을 삭제
  • 최종 완성된 컨테이너를 Customized Jenkins Builder Docker 이미지로 저장, docker.io 레지스트리에 Push
[root@kubemaster 00-jenkins-custom-image]# export NAMESPACE=ns-jenkins
[root@kubemaster 00-jenkins-custom-image]# export JENKINS_POD=$(kubectl get pods -n $NAMESPACE | grep jenkins-leader | awk '{print $1}')
[root@kubemaster 00-jenkins-custom-image]# export DOCKER_ACCOUNT=drlee001

[root@kubemaster 00-jenkins-custom-image]# mkdir -p docker/jenkins-kubernetes-leader

[root@kubemaster 00-jenkins-custom-image]# kubectl cp ${NAMESPACE}/${JENKINS_POD}:var/jenkins_home/config.xml \
  docker/jenkins-kubernetes-leader/config.xml
[root@kubemaster 00-jenkins-custom-image]# kubectl cp ${NAMESPACE}/${JENKINS_POD}:var/jenkins_home/users/ \
  docker/jenkins-kubernetes-leader/users/
[root@kubemaster 00-jenkins-custom-image]# kubectl cp ${NAMESPACE}/${JENKINS_POD}:var/jenkins_home/jobs/ \
  docker/jenkins-kubernetes-leader/jobs/
[root@kubemaster 00-jenkins-custom-image]# kubectl cp ${NAMESPACE}/${JENKINS_POD}:var/jenkins_home/secrets/master.key \
  docker/jenkins-kubernetes-leader/secrets/master.key
[root@kubemaster 00-jenkins-custom-image]# kubectl cp ${NAMESPACE}/${JENKINS_POD}:var/jenkins_home/secrets/hudson.util.Secret \
  docker/jenkins-kubernetes-leader/secrets/hudson.util.Secret
[root@kubemaster 00-jenkins-custom-image]# kubectl cp ${NAMESPACE}/${JENKINS_POD}:var/jenkins_home/secrets/slave-to-master-security-kill-switch \
  docker/jenkins-kubernetes-leader/secrets/slave-to-master-security-kill-switch
[root@kubemaster 00-jenkins-custom-image]# curl -sSL "http://admin:admin@10.255.10.171:30500/pluginManager/api/xml?depth=1&xpath=/*/*/shortName|/*/*/version&wrapper=plugins" | \
  perl -pe 's/.*?<shortName>([\w-]+).*?<version>([^<]+)()(<\/\w+>)+/\1 \2\n/g'|sed 's/ /:/' > \
  docker/jenkins-kubernetes-leader/plugins.txt 

[root@kubemaster 00-jenkins-custom-image]# vi docker/jenkins-kubernetes-leader/executors.groovy
import jenkins.model.*
Jenkins.instance.setNumExecutors(0)

[root@kubemaster 00-jenkins-custom-image]# docker login -u ${DOCKER_ACCOUNT}

[root@kubemaster 00-jenkins-custom-image]# vi docker/jenkins-kubernetes-leader/Dockerfile
USER root
RUN apt-get update -y
USER ${user}

COPY config.xml /usr/share/jenkins/ref/config.xml
COPY executors.groovy /usr/share/jenkins/ref/init.groovy.d/executors.groovy
COPY jobs /usr/share/jenkins/ref/jobs
COPY secrets /usr/share/jenkins/ref/secrets
COPY users /usr/share/jenkins/ref/users
COPY plugins.txt /usr/share/jenkins/plugins.txt

# Workaround for 'Lockfile creation - File not found' error
RUN xargs /usr/local/bin/install-plugins.sh < /usr/share/jenkins/plugins.txt

RUN echo 2.0 > /usr/share/jenkins/ref/jenkins.install.UpgradeWizard.state

ENTRYPOINT ["/bin/tini", "--", "/usr/local/bin/jenkins.sh"]
[root@kubemaster 00-jenkins-custom-image]# docker build -t ${DOCKER_ACCOUNT}/jenkins-leader:2.60.3-ns-version --rm --no-cache docker/jenkins-kubernetes-leader/
[root@kubemaster 00-jenkins-custom-image]# docker push ${DOCKER_ACCOUNT}/jenkins-leader:2.60.3-ns-version
[root@kubemaster 00-jenkins-custom-image]# docker build -t ${DOCKER_ACCOUNT}/jenkins-leader:latest --rm --no-cache docker/jenkins-kubernetes-leader/
[root@kubemaster 00-jenkins-custom-image]# docker push ${DOCKER_ACCOUNT}/jenkins-leader:latest


- Barracuda -



블로그 이미지

Barracuda

Bryan의 Tech-Log. 기록은 역사다. 나는 역사를 공유하고 그 안에서 배우며, 또 다른 역사를 써나간다

댓글을 달아 주세요

Kubernetes 를 사용하다 보면 서로 분리된 환경에 설치된 여러 클러스터에 번갈아 접속해야 할 경우가 종종 발생한다. 예를 들어 한 쪽은 개발&테스트, 다른 한 쪽은 실제 서비스 클러스터 환경, 이런 식이다. 접속 터미널을 따로 띄워서 간편히 사용하는 경우가 많겠지만, 필요에 따라 하나의 kubectl 클라이언트로 서로 다른 클러스터에 접속할 수 있도록 설정하는 방법을 정리한다(K8s 1.6.x~1.8.x 기준).




클러스터 이름과 관리자 이름 변경


Kubernetes 를 구글 repo를 통해 기본 설치로 진행하면, kubectl을 통한 클러스터 접속용 설정 파일(yaml 형식)은 위의 캡처 이미지와 같은 모양으로 나타나게 된다(root 유저일 경우 KUBECONFIG 환경변수의 값은 '/root/.kube/config' 값을 가진다). 파일을 열어 보면, 클러스터명은 kubernetes, 사용자는 kubernetes-admin' 으로 자동 설정 되고, kubeadm을 통한 설치시에 만들어 진 보안 인증키 값들이 인코딩 되어 사용된다. Minikube 를 로컬 PC에 자체 설치하였어도 전체적인 설정파일의 구조는 동일하다고 볼 수 있다.


이와 같이, 유사한 방식으로 쿠버네티스를 여러 환경에 설치하더라도 해당 클러스터명과 사용자명이 동일하게 되지 않도록 구분을 지어 주어야만 context를 스위칭하면서 kubectl 에서 각각의 클러스터에 접속을 할 수 있게 된다.



캡처이미지에도 표시하였지만, 박스로 표시된 부분에 대해 각 이름의 일관성을 유지하면서 원하는 값으로 바꾸어 저장하도록 한다. 여기서는 클러스터 이름을 k8s-vbox로, 사용자(관리자) 이름을 k8s-vbox-admin 으로 변경한다.


<첫 번 째 클러스터 KUBECONFIG 파일 변경>

[root@kubemaster ~]# kubectl config get-contexts

CURRENT   NAME                          CLUSTER      AUTHINFO           NAMESPACE

*         kubernetes-admin@kubernetes   kubernetes   kubernetes-admin


[root@kubemaster ~]# vi $KUBECONFIG 

apiVersion: v1

clusters:

- cluster:

    certificate-authority-data: REDACTED

    server: https://10.255.10.170:6443

  name: k8s-vbox

contexts:

- context:

    cluster: k8s-vbox

    user: k8s-vbox-admin

  name: k8s-vbox-admin@k8s-vbox

current-context: k8s-vbox-admin@k8s-vbox

kind: Config

preferences: {}

users:

- name: help

  user:

    as-user-extra: {}

- name: k8s-vbox-admin

  user:

    client-certificate-data: REDACTED 

    client-key-data: REDACTED


[root@kubemaster ~]# systemctl daemon-reload

[root@kubemaster ~]# systemctl restart kubelet


[root@kubemaster ~]# kubectl config get-contexts

CURRENT   NAME                      CLUSTER    AUTHINFO         NAMESPACE

*         k8s-vbox-admin@k8s-vbox   k8s-vbox   k8s-vbox-admi



이번에는 또 다른 클러스터 이름을 k8s-vmw로, 사용자(관리자) 이름을 k8s-vmw-admin 으로 변경한다.


<두 번 째 클러스터 KUBECONFIG 파일 변경>

[root@kubemaster ~]# kubectl config get-contexts

CURRENT   NAME                          CLUSTER      AUTHINFO           NAMESPACE

*         kubernetes-admin@kubernetes   kubernetes   kubernetes-admin


[root@kubemaster ~]# cat $KUBECONFIG 

apiVersion: v1

clusters:

- cluster: 

    certificate-authority-data: REDACTED

    server: https://192.168.60.170:6443

  name: k8s-vmw

contexts:

- context:

    cluster: k8s-vmw

    user: k8s-vmw-admin

  name: k8s-vmw-admin@k8s-vmw

current-context: k8s-vmw-admin@k8s-vmw

kind: Config

preferences: {}

users:

- name: k8s-vmw-admin

  user:

    client-certificate-data: REDACTED

    client-key-data: REDACTED


[root@kubemaster ~]# systemctl daemon-reload 

[root@kubemaster ~]# systemctl restart kubelet

[root@kubemaster ~]# systemctl status kubelet

● kubelet.service - kubelet: The Kubernetes Node Agent

   Loaded: loaded (/etc/systemd/system/kubelet.service; enabled; vendor preset: disabled)

  Drop-In: /etc/systemd/system/kubelet.service.d

           └─10-kubeadm.conf

   Active: active (running) since 화 2017-12-19 02:07:47 EST; 9s ago

...


[root@kubemaster ~]# kubectl config get-contexts

CURRENT   NAME                    CLUSTER   AUTHINFO        NAMESPACE

*         k8s-vmw-admin@k8s-vmw   k8s-vmw   k8s-vmw-admin



두 개 클러스터의 config 병합 & kubectl 접속


각 클러스터마다 config 를 수정하여 이름 부분이 잘 변경 되었다면, 양쪽 클러스터에 모두 접속할 수 있는 kubectl 클라이언트 계정의 config 파일에 또 다른 클러스터의 config 내용을 병합하여 아래와 같이 변경해 둔다. 내용을 잘 보면 두 개 클러스터의 config 파일 내용 중 'clusters' 영역과 'contexts' 영역 그리고 'users' 영역의 내용이 각각 병합되어 구성됨을 알 수 있을 것이다. 


[root@kubemaster ~]# cd ,kube

[root@kubemaster .kube]# cp config config.old

[root@kubemaster .kube]# vi config

apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: REDACTED
    server: https://10.255.10.170:6443
  name: k8s-vbox
- cluster:
    certificate-authority-data: REDACTED
    server: https://192.168.60.170:6443
  name: k8s-vmw
contexts:
- context:
    cluster: k8s-vbox
    user: k8s-vbox-admin
  name: k8s-vbox-admin@k8s-vbox
- context:
    cluster: k8s-vmw
    user: k8s-vmw-admin
  name: k8s-vmw-admin@k8s-vmw
current-context: k8s-vbox-admin@k8s-vbox
kind: Config
preferences: {}
users:
- name: help
  user:
    as-user-extra: {}
- name: k8s-vbox-admin
  user:
    client-certificate-data: REDACTED
    client-key-data: REDACTED
- name: k8s-vmw-admin
  user:
    client-certificate-data: REDACTED
    client-key-data: REDACTED



이제 다음과 같이 두 개의 클러스터를 전환하면서 kubectl 명령을 사용할 수 있게 된다.


[root@kubemaster .kube]# kubectl config get-contexts 

CURRENT   NAME                      CLUSTER    AUTHINFO         NAMESPACE

*         k8s-vbox-admin@k8s-vbox   k8s-vbox   k8s-vbox-admin   

          k8s-vmw-admin@k8s-vmw     k8s-vmw    k8s-vmw-admin


[root@kubemaster ~]# kubectl get pods -nkube-system -l tier=control-plane -o wide

NAME                                 READY     STATUS    RESTARTS   AGE       IP              NODE

etcd-kubemaster                      1/1       Running   5          4d        10.255.10.170   kubemaster

kube-apiserver-kubemaster            1/1       Running   6          4d        10.255.10.170   kubemaster

kube-controller-manager-kubemaster   1/1       Running   5          4d        10.255.10.170   kubemaster

kube-scheduler-kubemaster            1/1       Running   5          4d        10.255.10.170   kubemaster

* 현재 접속된 클러스터의 pod 구성을 확인(control-plane 에 해당하는 pod 정보만 조회)


[root@kubemaster ~]# kubectl config use-context k8s-vmw-admin@k8s-vmw

Switched to context "k8s-vmw-admin@k8s-vmw".


[root@kubemaster ~]# kubectl get pods -nkube-system -l tier=control-plane -o wide

NAME                                 READY     STATUS    RESTARTS   AGE       IP               NODE

etcd-kubemaster                      1/1       Running   36         50d       192.168.60.170   kubemaster

kube-apiserver-kubemaster            1/1       Running   17         50d       192.168.60.170   kubemaster

kube-controller-manager-kubemaster   1/1       Running   45         50d       192.168.60.170   kubemaster

kube-scheduler-kubemaster            1/1       Running   43         50d       192.168.60.170   kubemaster

* 접속할 클러스터를 전환하여 pod 구성을 확인




- Barracuda -



블로그 이미지

Barracuda

Bryan의 Tech-Log. 기록은 역사다. 나는 역사를 공유하고 그 안에서 배우며, 또 다른 역사를 써나간다

댓글을 달아 주세요


RBAC(Role Based Access Control). 우리 말로는 "역할 기반 접근 제어" 라고들 표현하지만 사실, '역할' 보다는 '권한' 이라고 이해하는 것이 더  뜻이 잘 통한다. Kubernetes 1.6 버전부터 kubeadm 을 통한 Bootstrap 방식의 설치가 도입되면서 RBAC의 기본 설정이 이루어지게 되어 있는대, 구글링을 통해 접해 본 예전 문서들에서는 curl 을 통해 쉽게 접근 되던 API 들이, 권한이 없다는 메시지를 뿌리며 실패되는 모습을 자주 보게 되면서, 이 부분에 대해 직접 실험하고 규명해 보고자 하는 생각이 들기 시작했다.






Kubeadm 의 bootstrapping 에 의해 설정된 기본 RBAC 확인


먼저 마스터에서 아래의 명령을 실행해 보자.


# APISERVER=$(kubectl config view | grep server | cut -f 2- -d ":" | tr -d " ")

# TOKEN="$(kubectl get secret $(kubectl get secrets | grep default | cut -f1 -d ' ') -o jsonpath='{$.data.token}' | base64 --decode)"

# curl -D - --insecure --header "Authorization: Bearer $TOKEN" $APISERVER/api/v1

HTTP/1.1 200 OK

Content-Type: application/json

Date: Tue, 05 Dec 2017 06:19:22 GMT

Transfer-Encoding: chunked


{

  "kind": "APIResourceList",

  "groupVersion": "v1",

  "resources": [

    {

...

...

...

      "verbs": [

        "get",

        "patch",

        "update"

      ]

    }

  ]

}

* APISERVER 변수에는 마스터에서 실행되고 있는 apiserver의 URL 값이 저장된다

* TOKEN 변수에는 default 네임스페이스의 default-token-xxxxx 토큰의 secret key 값이 decode 되어 저장된다

* 토큰 키를 header에 실어서 api 목록을 가져오는 curl 호출을 실행해 본다



위의 결과를 보면 API 가 잘 호출되고 모든 것이 순조로운 것처럼 보인다. 그렇다면  연이어서 아래 명령을 실행해 보자.


# curl -D - --insecure --header "Authorization: Bearer $TOKEN" $APISERVER/api/v1/pods

HTTP/1.1 403 Forbidden

Content-Type: application/json

X-Content-Type-Options: nosniff

Date: Tue, 05 Dec 2017 06:28:47 GMT

Content-Length: 292


{

  "kind": "Status",

  "apiVersion": "v1",

  "metadata": {

    

  },

  "status": "Failure",

  "message": "pods is forbidden: User \"system:serviceaccount:default:default\" cannot list pods at the cluster scope",

  "reason": "Forbidden",

  "details": {

    "kind": "pods"

  },

  "code": 403

}

* Default 네임스페이스의 pod 리스트를 가져오는 API 호출을 실행한다

* Default 네임스페이스의 default 라는 사용자는 자신의 네임스페이스임에도 pod의 리스트를 볼 수 없다는 결과를 보여 준다



뭔가 이상하지 않은가? 첫 번 째 curl 실행과는 달리 pod들의 목록을 가져오는데 403 - 권한 없음 - 이라는 결과를 보여 준다. 그렇다면 특정 네임스페이스의 서비스 목록을 가져 오는 것 또한 불가능하지는 않을까?


# curl -D - --insecure --header "Authorization: Bearer $TOKEN" $APISERVER/api/v1/namespaces/kube-system/services

HTTP/1.1 403 Forbidden

Content-Type: application/json

X-Content-Type-Options: nosniff

Date: Tue, 05 Dec 2017 06:34:55 GMT

Content-Length: 316


{

  "kind": "Status",

  "apiVersion": "v1",

  "metadata": {

    

  },

  "status": "Failure",

  "message": "services is forbidden: User \"system:serviceaccount:default:default\" cannot list services in the namespace \"kube-system\"",

  "reason": "Forbidden",

  "details": {

    "kind": "services"

  },

  "code": 403

}


맞다! Kube-system 네임스페이스의 서비스 목록을 가져오는 것도 역시 403의 결과를 보여 준다. 즉, RBAC 기본 설정대로라면 kubectl 을 통하지 않고 apiserver 에게 직접 API를 호출해서 클러스터의 상태를 보거나 설정 변경을 통한 관리, 운영 자체가 불가능한 상황이 되는 것이다. 우리는 앞으로 이 문제를 해결하는 방법을 찾아 보아야 한다.



그 전에, 우선 한 가지만 정리해 두고 넘어가자. 위의 명령들을 조합해서 RBAC 권한 설정에 대한 확인을 해 보려면  아래의 bash script 를 실행해 보면 된다.


# vi check-apiserver-access-by-default.sh

#!/bin/bash
# For v1.8.x default policy, this 'curl' results in '403 Forbidden'
APISERVER=$(kubectl config view | grep server | cut -f 2- -d ":" | tr -d " ")
# Retrieve 'default' account's TOKEN in 'default' namespace
TOKEN="$(kubectl get secret $(kubectl get secrets | grep default | cut -f1 -d ' ') -o jsonpath='{$.data.token}' | base64 --decode)"
curl -D - --insecure --header "Authorization: Bearer $TOKEN" $APISERVER/api/v1/namespaces/default/services

# chmod a+x check-apiserver-access-by-default.sh



Kubernetes 는 여러 개의 ServiceAccount 를 기본으로 제공하는데, Default 네임스페이스의 default 계정(ServiiceAccount, k8s 방식의 표현은 'default:default') 이 아닌 다른 계정은 어떨까? 아래의 명령을 실행해 보자(v1.9)


# APISERVER=$(kubectl config view | grep server | cut -f 2- -d ":" | tr -d " ")

# TOKEN="$(kubectl -nkube-system get secret $(kubectl get secrets -nkube-system | grep kube-dns | cut -f1 -d ' ') -o jsonpath='{$.data.token}' | base64 --decode)"

# curl -D - --insecure --header "Authorization: Bearer $TOKEN" $APISERVER/api/v1/namespaces/default/services

HTTP/1.1 200 OK

Content-Type: application/json

Date: Tue, 05 Dec 2017 06:57:21 GMT

Transfer-Encoding: chunked


{

  "kind": "ServiceList",

  "apiVersion": "v1",

...

...

      "status": {

        "loadBalancer": {

          

        }

      }

    }

  ]

}



앞의 시도와는 다르게, 이번에는 kubernetes의 control-plane 인 kube-system 네임스페이스의 kube-dns 계정(kube-system:kube-dns) 의 토큰 키 값을 읽어 와서 API 호출을 시도해 보니 정상적으로 동작한(v.19)


# curl -D - --insecure --header "Authorization: Bearer $TOKEN" $APISERVER/api/v1/namespaces/kube-system/services

HTTP/1.1 200 OK

Content-Type: application/json

Date: Tue, 05 Dec 2017 06:58:22 GMT

Transfer-Encoding: chunked


{

  "kind": "ServiceList",

  "apiVersion": "v1",

...

...

      "status": {

        "loadBalancer": {

          

        }

      }

    }

  ]

}


마찬가지로 kube-system 네임스페이스의 서비스 목록을 가져오는 것도 역시 가능하다. 그렇다면, API 호출을 통한 모든 행위(action)를 kube-system:kube-dns[각주:1] 계정으로 할 수 있는 것일까? 답은 "아니오" 이다. 맨 위의 그림이 그에 대한 설명을 보여 주고 있다. 다시 말하면 kube-system:kube-dns 계정(subject; 대상)에게는 전체 클러스터의 모든 서비스와 엔드포인트(cluster-wide, 즉 네임스페이스에 상관 없이) 들에 대해 'list', 'watch' 만 가능하도록 사전 설정 되어 있기 때문이다.



아래의 수행 결과는 현재 k8s 클러스터 내에서 위 그림의 파란 박스에 해당하는 system:kube-dns 라는 ClusterRole/ClusterRoleBinding 에 대한 설정 내용을 조회한 결과이다.


# kubectl describe clusterrole system:kube-dns

Name:         system:kube-dns

Labels:       kubernetes.io/bootstrapping=rbac-defaults

Annotations:  rbac.authorization.kubernetes.io/autoupdate=true

PolicyRule:

  Resources  Non-Resource URLs  Resource Names  Verbs

  ---------  -----------------  --------------  -----

  endpoints  []                 []              [list watch]

  services   []                 []              [list watch]


# kubectl describe clusterrolebinding system:kube-dns

Name:         system:kube-dns

Labels:       kubernetes.io/bootstrapping=rbac-defaults

Annotations:  rbac.authorization.kubernetes.io/autoupdate=true

Role:

  Kind:  ClusterRole

  Name:  system:kube-dns

Subjects:

  Kind            Name      Namespace

  ----            ----      ---------

  ServiceAccount  kube-dns  kube-system



기본 계정(ServiceAccount)에 클러스터 관리자 권한을 부여한다면?


Kubernetes 는 클러스터 전체를 관리할 수 있는 권한을 'cluster-admin' 이라는 'ClusterRole' 형태로 제공하고 있다. 다음의 yaml 파일을 적용해 보자.


#  vi 00-default-admin-access.yaml 

kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: cluster-admin-clusterrolebinding-1
subjects:
- kind: ServiceAccount
  name: default
  namespace: kube-system
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: cluster-admin-clusterrolebinding-2
subjects:
- kind: ServiceAccount
  name: default
  namespace: default
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin

# kubectl apply -f 00-default-admin-access.yaml 

clusterrolebinding "cluster-admin-clusterrolebinding-1" created

clusterrolebinding "cluster-admin-clusterrolebinding-2" created

* 모든 네임스페이스에는 default 라는 계정이 반드시 하나 존재하게 되는데, 이들 계정에 cluster-admin 에 부여된 클러스터 관리자 권한을 매핑(ClusterRoleBinding)하여 적용한다



위와 같이 해 놓고, 앞서 확인용으로 만들어 놓은 check-apiserver-access-by-default.sh 를 실행하여 API 실행 권한을 확인해 보자.


# ./check-apiserver-access-by-default.sh 

HTTP/1.1 200 OK

Content-Type: application/json

Date: Tue, 05 Dec 2017 07:35:09 GMT

Transfer-Encoding: chunked


{

  "kind": "ServiceList",

  "apiVersion": "v1",

...

      "status": {

        "loadBalancer": {

          

        }

      }

    }

  ]

}



Default:default 계정으로 이전에는 동작하지 않던 default 네임스페이스의 서비스 목록 조회가 이번에는 정상적으로 동작한다(kube-system:default 계정으로도 마찬가지 결과를 확인할 수 있을 것이다).


그런데, 뭔가 석연치 않다. Kubernetes 의 기본 RBAC 설정을 망가뜨리고 있다는 생각이 들지 않는가? 바꿔 말해서 각 네임스페이스에 기본으로 존재하는 default 계정에 클러스터 전체를 관리할 수 있는 막강한 권한을 부여하는 것 자체가 보안성을 위협하는 심각한 장애 포인트가 될 수도 있다는 생각을 지울 수 없다. 일단 RBAC에 의한 권한 설정과 사용에 대한 감은 잡았으니, 깔끔하게 방금 설정한 내용을 지워 버리도록 하자..


# kubectl delete -f 00-default-admin-access.yaml 

clusterrolebinding "cluster-admin-clusterrolebinding-1" deleted

clusterrolebinding "cluster-admin-clusterrolebinding-2" deleted

* Yaml 파일 내용을 잘 보면, ClusterRoleBinding, 즉 권한 매핑을 지우는 것이지, default 계정들을 삭제하는 것은 아니므로 안심하자



클러스터 관리자 권한을 가지는 별도의 특별한 계정을 생성하고 사용


Kubernetes 클러스터 전체를 관리하는 유일하고 특별한 계정(kube-system:root-sa)를 따로 만들어서, 소수의 관리자 권한을 가진 사용자에게만 공개한다면 시스템 보안에 대한 위협은 훨씬 줄어들 것이다. 다음과 같이 yaml 파일을 작성하고 적용해 보자.


# vi 00-root-sa-admin-access.yaml 

kind: ServiceAccount
apiVersion: v1
metadata:
  name: root-sa
  namespace: kube-system
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: root-sa-kube-system-cluster-admin
subjects:
- kind: ServiceAccount
  name: root-sa
  namespace: kube-system
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin

# kubectl apply -f 00-root-sa-admin-access.yaml

clusterrolebinding "root-sa-kube-system-cluster-admin" created

* kube-system:root-sa 계정을 생성하고 cluster-admin 이라는 ClusterRole(권한 집합)과 매핑(ClusterRoleBinding)하여 클러스터 관리자 권한을 부여한다



참고로, 위의 yaml 로 정의된 root-sa 계정의 RBAC 설정은 다음 그림과 같이 이해하면 된다.




이제 앞서와 마찬가지로 방금 설정한 권한의 매핑 상태를 확인하는 bash script 를 하나 작성해 두자


# vi check-apiserver-access-by-root.sh

#!/bin/bash
# By modified RBAC policy, this 'curl' results in '200 OK'
APISERVER=$(kubectl config view | grep server | cut -f 2- -d ":" | tr -d " ")
# Retrieve 'root' account's TOKEN in 'kube-system' namespace
ROOTTOKEN="$(kubectl get secret -nkube-system $(kubectl get secrets -nkube-system | grep root-sa | cut -f1 -d ' ') -o jsonpath='{$.data.token}' | base64 --decode)"
curl -D - --insecure --header "Authorization: Bearer $ROOTTOKEN" $APISERVER/api/v1/namespaces/default/services

# chmod a+x check-apiserver-access-by-root.sh

* Kube-system:root-sa 계정의 토큰 키값을 사용해서 API 호출을 수행하는 bash script



현재 설정된 root-sa 의 권한을 확인하기 위해 위의 스크립트를 실행해 보자


# ./check-apiserver-access-by-root.sh 

HTTP/1.1 200 OK

Content-Type: application/json

Date: Tue, 05 Dec 2017 08:12:38 GMT

Transfer-Encoding: chunked


{

  "kind": "ServiceList",

  "apiVersion": "v1",

  "metadata": {

    "selfLink": "/api/v1/namespaces/default/services",

...

    }

  ]

}

* root-sa 계정으로 default 네임스페이스의 서비스 목록을 성공적으로 불러 오고 있음을 확인할 수 있다.



특정 대상(subject)에 적용된 권한 매핑(ClusterRoleBinding)과 권한 집합(ClusterRole) 조회


[Bonus]


Kubectl 명령으로 ClusterRole/ClusterRoleBinding 목록을 확인하려면,


# kubectl get clusterrole

NAME                                                                   AGE

admin                                                                  19d

cluster-admin                                                          19d

edit                                                                   19d

...

view                                                                   19d

weave-net                                                              19d


# kubectl get clusterrolebinding

NAME                                            AGE

cluster-admin                                   19d

heapster                                        19d

...

system:node                                     19d

system:node-proxier                             19d

weave-net                                       19d


와 같이 명령을 실행하면 되지만, 역으로 특정 대상(ServiceAccount/User/Group)과 연관된 ClusterRole이나 ClusterRoleBinding의 목록을  확인하고 싶을 경우 다음의 bash script 를 작성해서 사용한다면 요긴하게 쓰일 것이니 참고하여 보도록 하자. 단, 스크립트 내에서 JSON 입력을 파싱하는 기능을 사용하기 위해 jq(JSON processor)를 사용하므로 yum install jq 와 같이 jq 패키지를 설치해 두어야 한다.


# vi check-clusterrole.sh

#!/bin/bash
# $1 is kind (User, Group, ServiceAccount)
# $2 is name ("system:nodes", etc)
# $3 is namespace (optional, only applies to kind=ServiceAccount)
function getRoles() {
    local kind="${1}"
    local name="${2}"
    local namespace="${3:-}"

    kubectl get clusterrolebinding -o json | jq -r "
      .items[]
      | 
      select(
        .subjects[]?
        | 
        select(
            .kind == \"${kind}\" 
            and
            .name == \"${name}\"
            and
            (if .namespace then .namespace else \"\" end) == \"${namespace}\"
        )
      )
      |
      (.metadata.name + \" \" + .roleRef.kind + \"/\" + .roleRef.name)
    "
}

if [ $# -lt 2 ]; then
    echo "Usage: $0 kind name {namespace}"
    exit -1
fi

echo "* Query: "
echo " - kind: $1"
echo " - name: $2"
echo " - namespace: $3"
echo "* Result: "
echo "[ClusterRolebinding] [Clusterrole.kind/Clusterrole.name]"
getRoles $1 $2 $3

# chmod a+x check-clusterrole.sh

* Kubernetes 에서 권한(Role)을 매핑 또는 부여(Binding)하는 대상(subject)는 ServiceAccount/User/Group 중 하나이다 

* ServiceAccount 는 네임스페이스에 종속적이므로 kind=ServiceAccount 일 때는 네임스페이스를 지정해야만 한다



사용법은 다음과 같다. 각각의 실행 결과를 직접 확인해 보면 kubernetes 에서 제공하는 RBAC 메커니즘에 에 대한 이해에 도움이 될 듯하다.


# ./check-clusterrole.sh 

Usage: ./check-clusterrole.sh kind name {namespace}


# ./check-clusterrole.sh ServiceAccount root-sa kube-system

* Query: 

 - kind: ServiceAccount

 - name: root-sa

 - namespace: kube-system

* Result: 

[ClusterRolebinding] [Clusterrole.kind/Clusterrole.name]

root-sa-kube-system-cluster-admin ClusterRole/cluster-admin


# ./check-clusterrole.sh Group system:unauthenticated

* Query: 

 - kind: Group

 - name: system:unauthenticated

 - namespace: 

* Result: 

[ClusterRolebinding] [Clusterrole.kind/Clusterrole.name]

system:basic-user ClusterRole/system:basic-user

system:discovery ClusterRole/system:discovery


# ./check-clusterrole.sh User system:kube-scheduler

* Query: 

 - kind: User

 - name: system:kube-scheduler

 - namespace: 

* Result: 

[ClusterRolebinding] [Clusterrole.kind/Clusterrole.name]

system:kube-scheduler ClusterRole/system:kube-scheduler



- Barracuda -



  1. kube-dns는 k8s 의 클러스터 내부에 존재하는 DNS로, 각 서비스나 엔드포인트들의 IP 주소를 내부 도메인명인 *.cluster.local 과 연결시켜 resolution을 수행한다 [본문으로]
블로그 이미지

Barracuda

Bryan의 Tech-Log. 기록은 역사다. 나는 역사를 공유하고 그 안에서 배우며, 또 다른 역사를 써나간다

댓글을 달아 주세요


Split Brain 은 Clustering 또는 다중 노드/스토리지 구성의 각종 솔루션들(주로 고가용성과 부하분산, 즉 HA 용도), 예를 들어 Redis-Sentinel, MariaDB Galera Cluster, GlusterFS 파일 분산복제 설정, Oracle RAC, vSphere HA 구성 등에서 중요한 장애 유발 요인이며, Production 환경에서 점검해야 할 중요한 아키텍처적 회피 대상 항목에 해당한다.




1. Split Brain 이 도대체 무엇일까?


문자 그대로 "뇌가 양쪽으로 분단 된" 모양 또는 상황 그자체를 표현한다. IT와 무관한 비유를 해 보자면, 머리 둘 달린 용이 왼쪽으로 갈지 오른쪽으로 갈지 몰라서 갈팡질팡하는 형국이라고 할 수도 있겠다. 실제로 인간의 뇌는 좌뇌와 우뇌로 구성되는데, 양쪽 뇌를 연결해주는 '뇌량'을 절단해 버리면 왼쪽 뇌와 오른쪽 뇌의 인지 부조화와 같은 심각한 부작용이 나타난다고 알려져 있다.


이번 글에서는 MySQL 호환 데이터베이스 클러스터링 솔루션인 MariaDB Galera Cluster 의 기본 3대 구성에서 장애/점검 등을 이유로 1 대가 빠진 상태를 구체적인 예를 들어 설명해 보도록 한다. 3대 구성이 기본인 이유는, Leader 의 선출(투표에 의한)시 과반수 이상의 득표가 가능한 구조여야 하기 때문이다. 즉 3명(Quorom[각주:1]=3)이 투표에 참여하여 2 이상의 득표자가 Leader가 되는 경우를 생각해 보면 되겠다.   



위의 첫 번 째 그림은 전형적인 Split Brain 의 가능성이 내재된 상황을 표현하고 있다. 그렇다면 이 상황에서 100% Split Brain 이 발생한다고 단정할 수 있는가 하면 그건 아니다. 즉 물리적인 연결 구성과 함께 Network Topology를 보고 판단해야 하기 때문이다. 다음 그림을 보자.



만약 처음에 보았던 논리적 Diagram의 실제 네트워크 구성이 이 그림과 같이 단일 네트워크로 이루어져 있고 DB Client(또는 WAS)에서 Load Balancer를 거쳐서 Galera-1, Galera-2 로 접속이 되는 방식이라면, Split Brain은 발생하지 않는다. 따라서 Galera-1 서버가 다운되거나 네트워크 접속이 끊기면, DB Client는 정상적으로 Galera-2 로 접속되고 Galera-2는 자신을 DB Master로 인식하여 정상 작동을 하게 된다. 위의 구성도를 Topology로 표현하면 아래의 그림과 유사한 모양이 된다.




그렇다면 도대체 어떤 상황에서 Split Brain 이 발생할 수 있다는 말일까? 다음의 2개의 그림이 바로 그에 대한 답이 된다.



위의 네트워크 구성도에서, Galera-1과 Galera-2 사이의 데이터 복제 및 Alive-check는 Switch-2의 DB망을 통해서 이루어 지며, 실제 Production 환경에서의 전형적 네트워크 구성도의 예시를 들어 보자면 개략적으로 다음 그림과 같은 모습이 될 것이다.




위의 2가지 네트워크 구성도의 Topology를 그려 보면, 공통적으로 아래의 그림과 같이 나타난다. 이 상황에서 Galera-1, Galera-2 사이의 경로가 끊어지면, 바로 Split Brain 상황이 발생하게 되는데, DB Client 입장에서는 2개 DB 모두로의 접속 자체는 가능하지만, 데이터베이스 관련 각종 쿼리(use database, select, insert 문 등)가 실패 되는 현상이 발생하게 된다. 다시 전문용어를 써서 상세히 표현하자면, "네트워크의 부분적인 장애로 DB접속자(Client, WAS)에게는 2개의 DB 서버 모두 정상적으로 보이지만, 각 DB 서버는 상대방이 비정상이라고 판단하여 데이터 동기화 및 읽기 쓰기가 비정상 상태에 빠지는 것" 이라고 할 수 있으며, 다른 말로 Network Partition 또는 Cluster Partition 이라고도 한다.


위의 구성도를 Topology로 표현하면 아래의 그림과 유사한 모양이 된다.




2. 정상적 Cluster 구성상태의 점검/확인 방법


* MariaDB Galera Cluster 의 설치 과정은 다음 링크를 참고한다

☞ http://bryan.wiki/246


위의 섹션 1에서 Split-Brain 상황이 발생한 구조와 같이 2대의 MariaDB를 사용하는 경우(3대 중 1대의 동작이 중지되어 2대가 남은 상태)와 동일한 구성을 갖추기 위해 다음의 2개 VM을 준비하고 /etc/my.cnf.d/server.cnf 내용을 각각 설정하여 정상적인 2대 구성의 Cluster 환경을 갖춘다. 



[maria1]

  • CentOS 7.3, MariaDB-server
  • NIC1(ens160): 192.168.30.105 - DB Client와 DB간 연결 네트워크
  • NIC2(ens192): 10.199.30.105 - DB간 연결 네트워크

#

# * Galera-related settings

#

[galera]

# Mandatory settings

wsrep_on=ON

wsrep_provider=/usr/lib64/galera/libgalera_smm.so

wsrep_cluster_address='gcomm://'

#wsrep_cluster_address='gcomm://10.199.30.105,10.199.30.106'

wsrep_cluster_name='galera'

wsrep_node_address='10.199.30.105'

wsrep_node_name='maria1'

wsrep_sst_method=rsync


binlog_format=row

default_storage_engine=InnoDB

innodb_autoinc_lock_mode=2

#

# Allow server to accept connections on all interfaces.

#

bind-address=0.0.0.0

#

# Optional setting

#wsrep_slave_threads=1

innodb_flush_log_at_trx_commit=2



[maria2]

  • CentOS 7.3, MariaDB-server
  • NIC1(ens160): 192.168.30.106 - DB Client와 DB간 연결 네트워크
  • NIC2(ens192): 10.199.30.106 - DB간 연결 네트워크

#

# * Galera-related settings

#

[galera]

# Mandatory settings

wsrep_on=ON

wsrep_provider=/usr/lib64/galera/libgalera_smm.so

wsrep_cluster_address='gcomm://10.199.30.105,10.199.30.106'

wsrep_cluster_name='galera'

wsrep_node_address='10.199.30.106'

wsrep_node_name='maria2'

wsrep_sst_method=rsync


binlog_format=row

default_storage_engine=InnoDB

innodb_autoinc_lock_mode=2

#

# Allow server to accept connections on all interfaces.

#

bind-address=0.0.0.0

#

# Optional setting

#wsrep_slave_threads=1

innodb_flush_log_at_trx_commit=2



Cluster의 정상적인 동작상황에서는 다음의 4가지 점검 사항을 확인해 보고, 필요 시 튜닝 또는 설정값 조정을 진행해야 한다.


1. Cluster Integrity Check

 Maria1

Maria2 

select * from information_schema.GLOBAL_STATUS where VARIABLE_NAME like 'wsrep_cluster_state_uuid' or VARIABLE_NAME like 'wsrep_cluster_conf_id' or VARIABLE_NAME like 'wsrep_cluster_size' or VARIABLE_NAME like 'wsrep_cluster_status';

+--------------------------+--------------------------------------+

| VARIABLE_NAME | VARIABLE_VALUE |

+--------------------------+--------------------------------------+

| WSREP_CLUSTER_CONF_ID | 2 |

| WSREP_CLUSTER_SIZE | 2 |

| WSREP_CLUSTER_STATE_UUID | b04a71b5-161b-11e7-b1e1-bbd969deabbb |

| WSREP_CLUSTER_STATUS | Primary |

+--------------------------+--------------------------------------+

select * from information_schema.GLOBAL_STATUS where VARIABLE_NAME like 'wsrep_cluster_state_uuid' or VARIABLE_NAME like 'wsrep_cluster_conf_id' or VARIABLE_NAME like 'wsrep_cluster_size' or VARIABLE_NAME like 'wsrep_cluster_status';

+--------------------------+--------------------------------------+

| VARIABLE_NAME | VARIABLE_VALUE |

+--------------------------+--------------------------------------+

| WSREP_CLUSTER_CONF_ID | 2 |

| WSREP_CLUSTER_SIZE | 2 |

| WSREP_CLUSTER_STATE_UUID | b04a71b5-161b-11e7-b1e1-bbd969deabbb |

| WSREP_CLUSTER_STATUS | Primary |

+--------------------------+--------------------------------------+

 수행결과 캡처

 


 항목별 점검/확인 포인트

  • WSREP_CLUSTER_STATE_UUID : 모든 노드에서 동일해야 함. 값이 다른 노드는 Cluster 에서 제외되었음을 의미
  • WSREP_CLUSTER_CONF_ID : 위와 같음. 단 Cluster가 단절되었다가 복구될 때마다 +1 씩 값이 증가함
  • WSREP_CLUSTER_SIZE : Cluster 내의 노드 갯수
  • WSREP_CLUSTER_STATUS : 모든 쓰기 가능한 노드는 Primary 이어야 함



2. Node Status Check

 Maria1

Maria2 

select * from information_schema.GLOBAL_STATUS where VARIABLE_NAME like 'wsrep_ready' or VARIABLE_NAME like 'wsrep_connected' or VARIABLE_NAME like 'wsrep_local_state_comment';

+---------------------------+----------------+

| VARIABLE_NAME | VARIABLE_VALUE |

+---------------------------+----------------+

| WSREP_CONNECTED | ON |

| WSREP_LOCAL_STATE_COMMENT | Synced |

| WSREP_READY | ON |

+---------------------------+----------------+

select * from information_schema.GLOBAL_STATUS where VARIABLE_NAME like 'wsrep_ready' or VARIABLE_NAME like 'wsrep_connected' or VARIABLE_NAME like 'wsrep_local_state_comment';

+---------------------------+----------------+

| VARIABLE_NAME | VARIABLE_VALUE |

+---------------------------+----------------+

| WSREP_CONNECTED | ON |

| WSREP_LOCAL_STATE_COMMENT | Synced |

| WSREP_READY | ON |

+---------------------------+----------------+

 수행결과 캡처

 


 항목별 점검/확인 포인트

  • WSREP_READY : true(ON) 이면 해당 노드는 SQL 처리가 가능한 상태
  • WSREP_CONNECTED : true(ON) 이면 정상, WSREP_LOCAL_STATE_COMMENT 값이 'Synced'.
  • false(OFF) 이면 해당 노드가 정상적으로 Cluster 에 참여하지 못한 상태로 WSREP_LOCAL_STATE_COMMENT 값을 참조해야 함
  • WSREP_LOCAL_STATE_COMMENT : 동기화 진행중이면 Joining/Waiting for SST/Joined 중 하나의 값




3. Replication Status  Check

 Maria1

Maria2 

select * from information_schema.GLOBAL_STATUS where VARIABLE_NAME like 'wsrep_flow_control_paused' or VARIABLE_NAME like 'wsrep_cert_deps_distance';

+---------------------------+----------------+

| VARIABLE_NAME | VARIABLE_VALUE |

+---------------------------+----------------+

| WSREP_CERT_DEPS_DISTANCE | 0.000000 |

| WSREP_FLOW_CONTROL_PAUSED | 0.000000 |

+---------------------------+----------------+

select * from information_schema.GLOBAL_STATUS where VARIABLE_NAME like 'wsrep_flow_control_paused' or VARIABLE_NAME like 'wsrep_cert_deps_distance';

+---------------------------+----------------+

| VARIABLE_NAME | VARIABLE_VALUE |

+---------------------------+----------------+

| WSREP_CERT_DEPS_DISTANCE | 0.000000 |

| WSREP_FLOW_CONTROL_PAUSED | 0.000000 |

+---------------------------+----------------+

 수행결과 캡처

 


 항목별 점검/확인 포인트

  • WSREP_FLOW_CONTROL_PAUSED : 마지막 상태점검 시간부터 현재까지의 전체 시간 중 Replication이 중지된 시간의 비율. 0에 가까울수록 Lag이 적음을 의미하고 1이면 완전 중지. DB의 전체적 성능이 저하되었다고 판단될 경우 Cluster 전체 노드 중, 이 값이 가장 큰 노드(Slave Lag이 커서 전체 성능에 영향을 줌)을 제거해야 함
  • WSREP_CERT_DEPS_DISTANCE : 병렬처리 가능한 트랜잭션 수. 튜닝시는 이 값보다 적당히 크게 wsrep_slave_threads 정수값을 설정할 수 있음




4. Slow Cluster  Check

 Maria1

Maria2 

select * from information_schema.GLOBAL_STATUS where VARIABLE_NAME like 'wsrep_flow_control_sent' or VARIABLE_NAME like 'wsrep_local_recv_queue_avg' or VARIABLE_NAME like 'wsrep_local_send_queue_avg';

+----------------------------+----------------+

| VARIABLE_NAME | VARIABLE_VALUE |

+----------------------------+----------------+

| WSREP_FLOW_CONTROL_SENT | 0 |

| WSREP_LOCAL_RECV_QUEUE_AVG | 0.000000 |

| WSREP_LOCAL_SEND_QUEUE_AVG | 0.000000 |

+----------------------------+----------------+

select * from information_schema.GLOBAL_STATUS where VARIABLE_NAME like 'wsrep_flow_control_sent' or VARIABLE_NAME like 'wsrep_local_recv_queue_avg' or VARIABLE_NAME like 'wsrep_local_send_queue_avg';

+----------------------------+----------------+

| VARIABLE_NAME | VARIABLE_VALUE |

+----------------------------+----------------+

| WSREP_FLOW_CONTROL_SENT | 0 |

| WSREP_LOCAL_RECV_QUEUE_AVG | 0.000000 |

| WSREP_LOCAL_SEND_QUEUE_AVG | 0.000000 |

+----------------------------+----------------+

 수행결과 캡처



 항목별 점검/확인 포인트

  • WSREP_FLOW_CONTROL_SENT : 이 값이 클수록 처리 성능이 낮음
  • WSREP_LOCAL_RECV_QUEUE_AVG : 이 값이 클수록 처리 성능이 낮음
  • WSREP_LOCAL_SEND_QUEUE_AVG : 이 값이 클수록 네트워크 링크의 속도가 낮음(OS, 네트워크의 물리 구성 등 여러가지 영향 분석 필요)


위의 1, 2번 항목 점검에 의해 maria1, maria2 두개의 노드는 정상적 Cluster 상태를 유지하고 있음을 확인할 수 있다.



3. Split Brain 상태의 유발, 복구 및 쿼리 실행 결과 확인


maria1과 maria2 사이의 DB간 네트워크를 단절시키는 장애 상황을 시뮬레이션하기 위해 2대 중 1대에서 ifdown ens192(또는 iptables -A INPUT -d 10.199.30.106 -s 10.199.30.105 -j REJECT)를 실행하고, 양 DB서버간 복제/동기화를 위한 접속이 불가능함을 확인한다. 


아래 화면캡처 중간 부분의 DB client 측 터미널 화면에서 maria1, maria2 양쪽 DB 서버 모두에게 DB 접속은 가능하지만 실제 DB query는 실패 됨을 확인할 수 있다.




장애 요인(DB간 네트워크 단절)을 제거하여(원상 복구된 상황),  각 DB 서버의 상태를 점검, 데이터 복제 여부를 확인한 결과는 다음과 같이 나타나게 된다.





- Barracuda -


  1. 쿼럼; 정족수, 즉 투표=vote 에 의한 의사결정이 이루어 지기 위한 최소한의 구성원 수. 클러스터링 개념에서는 과반수=majority 득표를 위한 노드 또는 참여자 수로, 일반적으로는 최소 3개가 되어야 한다. [본문으로]
블로그 이미지

Barracuda

Bryan의 Tech-Log. 기록은 역사다. 나는 역사를 공유하고 그 안에서 배우며, 또 다른 역사를 써나간다

댓글을 달아 주세요


GitHub 에 새로운 repository를 생성할 때 어떻게 하시나요? 주로 github 접속/로그인-New Repository 클릭-이름입력-Create Repository 클릭-로컬 git 디렉토리 생성-git init-git add-commit-push 과정을 통해서 진행할텐데, 과정 자체가 어려운 건 아니지만 번거로운 점이 적지 않습니다. 이 과정을 간단한 스크립트를 만들어서 진행하면 어떨까요?





준비사항

  • GitHub Account: github.com 에 가입(It's free!)
  • Git client와 curl이 설치된 linux box



스크립트 작성 & 실행


여기서 개발자의 작업 PC는 CentOs/RedHat linux 박스로 하고, 우선 Github에 올릴 소스 작업 디렉토리가 필요하겠지요. 기존 작업 디렉토리가 아닌 완전히 새로운 것으로 해 볼텐데요, 이를 GitHub의 자신의 계정과 연결하는 식으로 진행할 겁니다. 응용하면, 기존 디렉토리도 당연히 비슷한 방식으로 연결 가능하겠지요.


현재 작업 PC의 계정이 git config 로 git 서버에 연결된 적이 없다는 것을 전제로 진행합니다.


스크립트를 만듭니다(아래 스크립트를 응용하면 GitHub Account와 Email, 프로젝트명을 실행인자로 전달해서 수행하는 것도 가능하겠네요. 필요하다면 ... ^^). 스크립트를 수행하면 현재 디렉토리 아래에 해당 작업 디렉토리가 생성되고 이것이 바로 GitHub repository와 연결 됩니다. 


# vi github-repo-set.sh

#!/bin/bash
# Set your GitHub username and email
export GITHUB_USERNAME="YourGithubAccount"
export GITHUB_EMAIL="youraccount@domain.com"

git config --global user.name "${GITHUB_USERNAME}"
git config --global user.email "${GITHUB_EMAIL}"
git config --global credential.helper cache
git config --global credential.helper 'cache --timeout=3600'

curl -u "${GITHUB_USERNAME}" https://api.github.com/user/repos -d '{"name":"my-test-project-001"}'

mkdir my-test-project-001
echo "# my-test-project-001" > my-test-project-001/README.md
cd my-test-project-001
git init
git add .
git commit -a -m "Create my-test-project-001 repository"
git remote add origin https://github.com/${GITHUB_USERNAME}/my-test-project-001.git
git push -u origin master

# chmod a+x github-repo-set.sh

# ./github-repo-set.sh

Enter host password for user 'YourGithubAccount': - 로그인 암호 입력 -

{

  "id": 123456789,

  "name": "my-test-project-001",

  "full_name": "YourGithubAccount/my-test-project-001",

  "owner": {

    "login": "YourGithubAccount",

    "id": 123456789,

    "url": "https://api.github.com/users/YourGithubAccount",

    "html_url": "https://github.com/YourGithubAccount",

...

  "git_url": "git://github.com/YourGithubAccount/my-test-project-001.git",

  "ssh_url": "git@github.com:YourGithubAccount/my-test-project-001.git",

  "clone_url": "https://github.com/YourGithubAccount/my-test-project-001.git",

...

}

Initialized empty Git repository in /yourdirectory/my-test-project-001/.git/

[master (root-commit) d5f9e80] Create my-test-project-001 repository

 1 file changed, 1 insertion(+)

 create mode 100644 README.md

Username for 'https://github.com': YourGithubAccount 입력

Password for 'https://YourGithubAccount@github.com': - 로그인 암호 입력 -

Counting objects: 3, done.

Writing objects: 100% (3/3), 253 bytes | 0 bytes/s, done.

Total 3 (delta 0), reused 0 (delta 0)

To https://github.com/YourGithubAccount/my-test-project-001.git

 * [new branch]      master -> master

Branch master set up to track remote branch master from origin.

* cache --timeout=3600 부분은, Git 서버에 로그인한 세션이 1시간 동안 유지되는 것으로 필요에 따라 줄이거나 늘이면 되겠습니다



이제 GitHub 에 접속, 로그인해서 만들어진 프로젝트 repo를 확인해 봅니다. 아래 이미지는 저의 GitHub 계정인 DragOnMe 에 생성된 프로젝트의 캡처 화면입니다. 참 쉽죠잉~ ^^




#보너스/Repository 이름을 변수로 하여 스크립트 재작성


위의 스크립트는 테스트를 위한 일회성 스크립트이다. 이번에는 현재 YourProjectName 이라는 워킹 디렉토리와 하위에 파일이 존재할 경우에 반복적으로 써먹을 수 있도록 위의 스크립트를 일부 수정해 보자.


vi github-repo-set.sh

#!/bin/bash 
# Set your GitHub username and email 
export GITHUB_USERNAME="YourGithubAccount" 
export GITHUB_EMAIL="YourEmail@domain.com" 
export PROJECT_NAME="YourProjectName"
#
git config --global user.name "${GITHUB_USERNAME}" 
git config --global user.email "${GITHUB_EMAIL}" 
git config --global credential.helper cache 
git config --global credential.helper 'cache --timeout=3600' 
#
curl -u "${GITHUB_USERNAME}" https://api.github.com/user/repos -d '{"name":"'"$PROJECT_NAME"'"}' 
# mkdir ${PROJECT_NAME}
echo "# ${PROJECT_NAME}" > ${PROJECT_NAME}/README.md 
cd ${PROJECT_NAME}
git init 
git add . 
git commit -a -m "Create ${PROJECT_NAME} repository" 
git remote add origin https://github.com/${GITHUB_USERNAME}/${PROJECT_NAME}.git 
git push -u origin master

* PROJECT_NAME 변수에 레포지터리 이름을 할당해서 사용하고 있는데, 이 부분을 $1 으로 바꾸어 스크립트의 실행 인자로 레포지터리 이름을 넣어서 사용하하거나, 또는 변수로 사용하는 계정, 이메일 등과 함꼐 모두 바깥으로 빼서 실행 인자로 바꿔서 사용하는 방법도 생각해 볼 수 있다

 



- Barracuda -




블로그 이미지

Barracuda

Bryan의 Tech-Log. 기록은 역사다. 나는 역사를 공유하고 그 안에서 배우며, 또 다른 역사를 써나간다

Tag Github

댓글을 달아 주세요


K8s(쿠버네티스) 클러스터 내에 구현된 Application을 외부로 노출하여 서비스할 경우에 주로 Nginx의 LB 기능을 이용한 Ingress Load Balancer를 사용하게 된다. 기본적으로 제공되는 이러한 Nginx 컨테이너 방식 외에도 Haproxy, Traefik Ingress등의 3rd party 솔루션들이 존재하는데, 다음 part-2 에서는 최근 hot하게 뜨고 있는 Traefik Ingress를 사용한 구현을 다룰 예정이다.



필요 요소와 서비스 구성


  • Kubernetes Cluster(Local cluster, minikube or dind-cluster not in the cloud like AWS or GCP)
  • Backend Service(like web or specific app server)
  • 테스트 수행용 컨테이너(curl로 클러스터 내부 접속 테스트)
  • Local DNS(Cluster 외부, named 서버)


예제로 사용하는 백엔드 서비스는 단순한 web server로, 앞선 포스팅(http://bryan.wiki/287) 에서 만들어 본 웹서버 컨테이너 이미지(drlee001/kubeweb)을 사용한다. 





위 그림은 본 구현에서 적용된 전체 요소들의 논리적인 구성과 관계를 나타낸 것이다. 텍스트가 작아서 잘 보이지 않을 경우, 이미지를 클릭하여 원본 크기로 자세히 들여다 보도록 하자.



테스트 수행용 컨테이너 기동


클러스터 내부의 각 서비스에 대한 접속 테스트를 curl 로 수행할 때 사용할 Pod로, 네트워크를 테스트하기 위해 많이 사용하는 drlee001/net-tester 컨테이너를 기동해 둔다.


<00-test-pod.yaml>

apiVersion: v1
kind: Pod
metadata:
  name: net-tester-for-ingress
spec:
  containers:
  - image: drlee001/net-tester
    imagePullPolicy: IfNotPresent
    name: net-tester-container
  restartPolicy: Never

[root@kubemaster nginx-ingress-sample]# kubectl create -f 00-test-pod.yaml

[root@kubemaster nginx-ingress-sample]# kubectl get pods | grep net-tester

net-tester-for-ingress                      1/1       Running   0          13h



Backend Service 기동


Load Balancing 의 대상이 되는 예제 서비스로, 이미 만들어진 drlee001/kubeweb 컨테이너를 deploy 하고 ClusterIP 로 service를 기동해 둔다. 여기서는 2개의 서로 다른 서비스에 대해 하나의 Ingress를 통해 Load balancing이 가능하도록 deploy-test-1(port 7777로 접속) 과 deploy-test-2(port 8888로 접속)으로 구분하여 구축해 두도록 한다.


<01-deploy-2backends.yaml>

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: deploy-test-1
spec:
  replicas: 2
  template:
    metadata:
      labels:
        app: web-front-end
        department: group1
    spec:
      containers:
      - name: tiny-webserver-1
        image: drlee001/kubeweb
        env:
        - name: PORT_ARGS
          value: "--port=7777"
        ports:
        - containerPort: 7777
          name: web-port
          protocol: TCP
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: deploy-test-2
spec:
  replicas: 2
  template:
    metadata:
      labels:
        app: web-front-end
        department: group2
    spec:
      containers:
      - name: tiny-webserver-2
        image: drlee001/kubeweb
        env:
        - name: PORT_ARGS
          value: "--port=8888"
        ports:
        - containerPort: 8888
          name: web-port
          protocol: TCP

[root@kubemaster nginx-ingress-sample]# kubectl create -f 01-deploy-2backends.yaml

deployment "deploy-test-1" created

deployment "deploy-test-2" created

[root@kubemaster nginx-ingress-sample]# kubectl get deployment | grep deploy-test

deploy-test-1              2         2         2            2           13h

deploy-test-2              2         2         2            2           13h



다음으로, 2개의 서로 다른 deployment 에 대응하는 Cluster 내부 접속 endpoint 를 제공할 서비스를 구축한다.


<02-svc-2backends.yaml>

apiVersion: v1
kind: Service
metadata:
  name: backend-svc-1
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: web-port
  selector:
    app: web-front-end
    department: group1
---
apiVersion: v1
kind: Service
metadata:
  name: backend-svc-2
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: web-port
  selector:
    app: web-front-end
    department: group2

[root@kubemaster nginx-ingress-sample]# kubectl create -f 02-svc-2backends.yaml

service "backend-svc-1" created

service "backend-svc-2" created

[root@kubemaster nginx-ingress-sample]# kubectl get svc | grep backend-svc

backend-svc-1                          ClusterIP   10.133.108.20    <none>        80/TCP                         13h

backend-svc-2                          ClusterIP   10.131.128.20    <none>        80/TCP                         13h



Default backend 서비스 기동


Ingress LB를 통해서 들어온 request가, 등록된 Ingress rule(나중에 정의해서 사용할)에 매칭되지 않을 경우의 적당한 response 를 위해서, 이러한 request 를 받아줄 별도의 내부 서비스가 필요하다. 이를 default backend(일종의 랜딩페이지) 라고 하는데, port 8080으로 들어온 http 접속에 대해 하위 path가 '/' 인 경우 404(not found)를, '/healthz' 인 경우 200(ok) 를 응답하는 간단한 web server 이다. 여기서는 구글이 제공하는 defaultbackend 컨테이너를 사용한다.


<03-default-backend-for-ingress.yaml>

apiVersion: v1
kind: Service
metadata:
  name: default-http-backend
spec:
  type: ClusterIP
  ports:
  - port: 80
    protocol: TCP
    targetPort: 8080
  # Matches Deployment
  selector:
    app: default-http-backend
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: default-http-backend
spec:
  replicas: 2
  template:
    metadata:
      # Matches Deployment
      labels:
        app: default-http-backend
    spec:
      terminationGracePeriodSeconds: 60
      containers:
      - name: default-http-backend
        # Any image is permissable as long as:
        # 1. It serves a 404 page at /
        # 2. It serves 200 on a /healthz endpoint
        image: gcr.io/google_containers/defaultbackend:1.0
        livenessProbe:
          httpGet:
            path: /healthz
            port: 8080
            scheme: HTTP
          initialDelaySeconds: 30
          timeoutSeconds: 5
        ports:
        - containerPort: 8080
        resources:
          limits:
            cpu: 10m
            memory: 20Mi
          requests:
            cpu: 10m
            memory: 20Mi

[root@kubemaster nginx-ingress-sample]# kubectl create -f 03-default-backend-for-ingress.yaml

deployment "default-http-backend" created

service "default-http-backend" created

[root@kubemaster nginx-ingress-sample]# kubectl get svc | grep default-http

default-http-backend                   ClusterIP   10.142.34.97     <none>        80/TCP                         20h



테스트용 컨테이너로 서비스 정상 동작 확인


[root@kubemaster nginx-ingress-sample]# kubectl get svc

NAME                                   TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                        AGE

backend-svc-1                          ClusterIP   10.133.108.20    <none>        80/TCP                         20h

backend-svc-2                          ClusterIP   10.131.128.20    <none>        80/TCP                         20h

default-http-backend                   ClusterIP   10.142.34.97     <none>        80/TCP                         20h


[root@kubemaster nginx-ingress-sample]# kubectl exec -it net-tester-for-ingress -- curl http://backend-svc-1

<b> Pod/Hostname: deploy-test-1-76754b9b75-9p2lx Port: 7777</b><br/><br/>


[root@kubemaster nginx-ingress-sample]# kubectl exec -it net-tester-for-ingress -- curl http://backend-svc-2

<b> Pod/Hostname: deploy-test-2-6f57b84975-9jnf5 Port: 8888</b><br/><br/>


[root@kubemaster nginx-ingress-sample]# kubectl exec -it net-tester-for-ingress -- curl http://default-http-backend

 default backend - 404



Nginx Ingress Controller 설정값 저장을 위한 Configmap 등록


Nginx는 웹서버 기능과 함께 로드밸런서 역할도 수행할 수 있게 만들어져 있다. 편리하게도 kubernetes 클러스터 내에 이 설정 값을 configmap 형태로 등록해 두고, 필요에 따라 설정값을 바꿔가면서 운영할 수 있는데, 진행 과정 상 Nginx Ingress Controller를 띄우기 전에 미리 등록해 두어야 한다. 


<04-configmap-nginx-ingress-controller.yaml>

apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-ingress-controller-conf
  labels:
    app: nginx-ingress-lb
    group: lb
data:
  # for VTS page of the Nginx load balancer
  enable-vts-status: 'true'
  enable-sticky-sessions: 'true'

[root@kubemaster nginx-ingress-sample]# kubectl create -f 04-configmap-nginx-ingress-controller.yaml

configmap "nginx-ingress-controller-conf" created



Nginx Ingress Controller 실행


<05-deploy-nginx-ingress-controller.yaml>

# Add: SA, ClusterRole, ClusterRoleBinding
# Name Space: default
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: ingress
  namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  name: system:ingress
rules:
- apiGroups:
  - ""
  resources: ["configmaps","secrets","endpoints","events","services"]
  verbs: ["list","watch","create","update","delete","get"]
- apiGroups:
  - ""
  - "extensions"
  resources: ["services","nodes","ingresses","pods","ingresses/status"]
  verbs: ["list","watch","create","update","delete","get"]
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: ingress
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: system:ingress
subjects:
  - kind: ServiceAccount
    name: ingress
    # Could be like 'kube-system' ...
    namespace: default
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: nginx-ingress-controller
  # Could be like 'kube-system' ...
  namespace: default
spec:
  replicas: 2
  revisionHistoryLimit: 3
  template:
    metadata:
      labels:
        app: nginx-ingress-lb
    spec:
      serviceAccountName: ingress
      terminationGracePeriodSeconds: 60
      containers:
        - name: nginx-ingress-controller
          image: gcr.io/google_containers/nginx-ingress-controller:0.9.0-beta.2
          imagePullPolicy: IfNotPresent
          readinessProbe:
            httpGet:
              path: /healthz
              port: 18080
              scheme: HTTP
          livenessProbe:
            httpGet:
              path: /healthz
              port: 18080
              scheme: HTTP
            initialDelaySeconds: 10
            timeoutSeconds: 5
          args:
            - /nginx-ingress-controller
            - --default-backend-service=$(POD_NAMESPACE)/default-http-backend
            - --configmap=$(POD_NAMESPACE)/nginx-ingress-controller-conf
          env:
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            - name: POD_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
          ports:
            - containerPort: 80
            - containerPort: 18080

[root@kubemaster nginx-ingress-sample]# kubectl create -f 05-deploy-nginx-ingress-controller.yaml

deployment "nginx-ingress-controller" created


[root@kubemaster nginx-ingress-sample]# kubectl get deployment nginx-ingress-controller

NAME                       DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE

nginx-ingress-controller   2         2         2            2           20h



여기까지 진행하였다면 다음과 같은 여러 개의 pod 가 실행되고 있음을 확인할 수 있다.


[root@kubemaster nginx-ingress-sample]# kubectl get pods -o wide

NAME                                        READY     STATUS    RESTARTS   AGE       IP              NODE

default-http-backend-7c7764588c-8w4qg       1/1       Running   0          20h       10.38.0.6       kubenode3

default-http-backend-7c7764588c-qtbfk       1/1       Running   0          20h       10.32.0.5       kubenode1

deploy-test-1-76754b9b75-9p2lx              1/1       Running   0          21h       10.40.0.5       kubenode2

deploy-test-1-76754b9b75-hwnwc              1/1       Running   0          21h       10.38.0.5       kubenode3

deploy-test-2-6f57b84975-9jnf5              1/1       Running   0          21h       10.40.0.6       kubenode2

deploy-test-2-6f57b84975-zhrs8              1/1       Running   0          21h       10.32.0.3       kubenode1

net-tester-for-ingress                      1/1       Running   0          21h       10.32.0.2       kubenode1

nginx-ingress-controller-6679d99f68-gzkhb   1/1       Running   0          20h       10.40.0.7       kubenode2

nginx-ingress-controller-6679d99f68-wnshp   1/1       Running   0          20h       10.32.0.6       kubenode1


이 중에서 실제로 로드밸런서 역할을 담당할 pod가 nginx-ingress-controller-*-* 같이 생긴 pod들이다(사실 하나만 띄워도 되지만, 고가용성-HA-를 위해서는 2개 이상을 사용). net-tester를 통해서 각각의 nginx-ingress-controller 에 직접 접속이 되는지 확인해 보자. 아직은 따로 Ingress Rule을 적용하지 않았기 때문에 '404' 응답이 나오면 정상적인 상태이다(실은, 이 '404' 응답은 그림에서 보이듯이 default-http-backend 에서 보내주는 것이다).


[root@kubemaster nginx-ingress-sample]# kubectl exec -it net-tester-for-ingress -- curl http://10.40.0.7

default backend - 404

[root@kubemaster nginx-ingress-sample]# kubectl exec -it net-tester-for-ingress -- curl http://10.32.0.6

default backend - 404 



Ingress Rule 디플로이


이제 모든 준비는 끝났고, Nginx Ingress Controller 가 로드밸런서 역할을 수행할 수 있도록 Ingress Rule(Policy, 또는 Ingress Object) 를 디플로이하고 서비스로 노출(Expose)시키는 단계와, curl을 통한 접속 테스트, DNS 등록을 통한 실제 접속을 해 보는 일만 남았다. 아래 yaml을 보면 총 3개의 도메인에 대한 rule이 존재하는데, Nginx 또는 Apache httpd 등으로 웹서버를 운영해 보았다면, 하나의 웹서버에서 서로 다른 도메인에 대해 각각 특정한 웹페이지를 연결해 주는  'VirtualHost' 의 동작 방식과 유사함을 보게 될 것이다.


<06-ingress-rule.yaml>

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: nginx-ingress
  namespace: default
spec:
  rules:
  - host: kubeweb-7777.bryan.local
    http:
      paths:
      - backend:
          serviceName: backend-svc-1
          servicePort: 80
  - host: kubeweb-8888.bryan.local
    http:
      paths:
      - backend:
          serviceName: backend-svc-2
          servicePort: 80
  - host: kubeweb.bryan.local
    http:
      paths:
      - path: /svc1
        backend:
          serviceName: backend-svc-1
          servicePort: 80
      - path: /svc2
        backend:
          serviceName: backend-svc-2
          servicePort: 80
      - path: /nginx_status
        backend:
          serviceName: nginx-ingress-nodeport
          servicePort: 18080

[root@kubemaster nginx-ingress-sample]# kubectl create -f 06-ingress-rule.yaml

ingress "nginx-ingress" created

[root@kubemaster nginx-ingress-sample]# kubectl get ingress

NAME            HOSTS                                                                   ADDRESS   PORTS     AGE

nginx-ingress   kubeweb-7777.bryan.local,kubeweb-8888.bryan.local,kubeweb.bryan.local             80        20h


여기까지는 Kubernetes 클러스터 내부에서의 연결 통로만 설정이 완료된 상태다.


[root@kubemaster nginx-ingress-sample]# kubectl exec -it net-tester-for-ingress -- curl -H Host:kubeweb-7777.bryan.local http://10.40.0.7

<b> Pod/Hostname: deploy-test-1-76754b9b75-hwnwc Port: 7777</b><br/><br/>

[root@kubemaster nginx-ingress-sample]# kubectl exec -it net-tester-for-ingress -- curl -H Host:kubeweb-8888.bryan.local http://10.40.0.7

<b> Pod/Hostname: de