kubernetes + Kops + ExternalDNS 정리 – 2

kubernetes + kops + externalDNS 정리 두번째

첫번째 내용은 여기서 확인 가능합니다.

여기서는 Route53 연동을 위한 ExternalDNS를 알아봅니다.

kubernetes + kops with ExternalDNS


이제 service expose 가 필요한 Services 들을 위해 Route53 연동을 시작해봅시다.

Install ExternalDNS

external-dns/aws.md at master · kubernetes-incubator/external-dns · GitHub

위 링크에 가면 ExternalDNS on AWS 에 대한 설명이 나옵니다.
여기서 중요하게 집고 넘어가야할 부분이 IAM 설정입니다.
저는 처음에 kops IAM 설정에 위의 설정들이 다 들어가 있기 때문에 괜찮을 거라고 생각하고 넘어갔다가 상당히 고생했습니다.

위 링크에서 설명하고 있는 IAM은 k8s의 node role 에 대한 설정입니다.
kops 로 클러스터를 구성하면, master node와 nodes 들의 role들이 별도로 관리가. 됩니다.

아래에서 IAM permission for nodes 를 설정 하겠습니다.

Edit Cluster

kops 를 통해 cluster 에 ExternalDNS 사용을 위한 설정을 추가해줍니다.

kops edit cluster $NAME

위 명령 실행 시 vim 화면이 뜹니다.
내용을 살펴보면 cluster 설정에 대한 yaml 파일 입니다.

Route53 설정을 위해 아래 내용을 하단에 추가해줍니다.

  additionalPolicies:
    node: |
      [
        {
          "Effect": "Allow",
          "Action": [
            "route53:ChangeResourceRecordSets"
          ],
          "Resource": [
            "arn:aws:route53:::hostedzone/*"
          ]
        },
        {
          "Effect": "Allow",
          "Action": [
            "route53:ListHostedZones",
            "route53:ListResourceRecordSets"
          ],
          "Resource": [
            "*"
          ]
        }
      ]

위 내용은 node role 에 추가로 IAM policy 설정을 해준다는 의미입니다.
node 이외에도 master, bastion이 있습니다.
관련링크 입니다.
kops/iam_roles.md at master · kubernetes/kops · GitHub

이제 cluster 에 변경사항을 적용해줍니다.

kops update cluster --yes $NAME

이제 AWS console 에서 IAM 의 Role 에서 추가된 Policy 를 확인 하실 수 있습니다.

The additional Policy image

ExternalDNS on AWS


이제 어플을 배포 해보겠습니다.
배포하기전에 앞에 해야할 일이 ExternalDNS 를 kubernetes 에 생성하는 것입니다.

ExternalDNS 생성

external-dns/aws.md at master · kubernetes-incubator/external-dns · GitHub
위 링크에 있는 external-dns example 을 사용하겠습니다.
RBAC enabled 된 example 입니다.

external-dns.yaml

apiVersion: v1
kind: ServiceAccount
metadata:
  name: external-dns
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  name: external-dns
rules:
- apiGroups: [""]
  resources: ["services"]
  verbs: ["get","watch","list"]
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get","watch","list"]
- apiGroups: ["extensions"] 
  resources: ["ingresses"] 
  verbs: ["get","watch","list"]
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: external-dns-viewer
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: external-dns
subjects:
- kind: ServiceAccount
  name: external-dns
  namespace: default
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: external-dns
spec:
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: external-dns
    spec:
      serviceAccountName: external-dns
      containers:
      - name: external-dns
        image: registry.opensource.zalan.do/teapot/external-dns:v0.5.0
        args:
        - --source=service
        - --source=ingress
        - --domain-filter=k8s.example.com. # will make ExternalDNS see only the hosted zones matching provided domain, omit to process all available hosted zones
        - --provider=aws
        - --aws-zone-type=public # only look at public hosted zones (valid values are public, private or no value for both)

저장 후에 생성합니다.

kubectl create -f external-dns.yaml

Output:

serviceaccount "external-dns" created
clusterrole.rbac.authorization.k8s.io "external-dns" created
clusterrolebinding.rbac.authorization.k8s.io "external-dns-viewer" created
deployment.extensions "external-dns" create

Pod가 잘 실행 되었는지 확인

kubectl get po

Output

9to5-macbook:~/projects/k8s ktg$ kubectl get po
NAME                            READY     STATUS    RESTARTS   AGE
curl-545bbf5f9c-rlpwb           1/1       Running   1          6d
external-dns-75d57587fc-dkk8s   1/1       Running   0          43s
hello-go-6db785ccbd-94vdh       1/1       Running   0          6d
nginx-proxy-7b775767b6-fngcz    1/1       Running   0          6d

ExternalDNS Pod 의 로그 확인

kubectl logs -f $(kubectl get po -l app=external-dns -o name)

Output:

9to5-macbook:~/projects/k8s ktg$ kubectl logs -f $(kubectl get po -l app=external-dns -o name)
time="2018-05-17T09:18:45Z" level=info msg="config: {Master: KubeConfig: Sources:[service ingress] Namespace: AnnotationFilter: FQDNTemplate: CombineFQDNAndAnnotation:false Compatibilit$
: PublishInternal:false Provider:aws GoogleProject: DomainFilter:[k8stest.example.com.] ZoneIDFilter:[] AWSZoneType:public AWSAssumeRole: AzureConfigFile:/etc/kubernetes/azure.json AzureRe
sourceGroup: CloudflareProxied:false InfobloxGridHost: InfobloxWapiPort:443 InfobloxWapiUsername:admin InfobloxWapiPassword: InfobloxWapiVersion:2.3.1 InfobloxSSLVerify:true DynCustomerN
ame: DynUsername: DynPassword: DynMinTTLSeconds:0 InMemoryZones:[] PDNSServer:http://localhost:8081 PDNSAPIKey: Policy:sync Registry:txt TXTOwnerID:default TXTPrefix: Interval:1m0s Once:
false DryRun:false LogFormat:text MetricsAddress::7979 LogLevel:info}"
time="2018-05-17T09:18:45Z" level=info msg="Connected to cluster at https://100.64.0.1:443"
time="2018-05-17T09:18:46Z" level=info msg="All records are already up to date"
...

정상 동작을 확인했습니다.

이제 실제 우리가 서비스에 사용할 app 을 배포해봅시다.
여기서 예제는 nginx 입니다.

NGINX 배포

nginx.yaml

apiVersion: v1
kind: Service
metadata:
  name: nginx
  annotations:
    external-dns.alpha.kubernetes.io/hostname: nginx.k8s.example.com.
spec:
  type: LoadBalancer
  ports:
  - port: 80
    name: http
    targetPort: 80
  selector:
    app: nginx
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: nginx
spec:
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx
        name: nginx
        ports:
        - containerPort: 80
          name: http

Deploy and Service 생성

9to5-macbook:~/projects/k8s ktg$ kubectl create -f example/nginx.yaml
service "nginx" created
deployment.extensions "nginx" created

Service nginx 정보 확인

9to5-macbook:~/projects/k8s ktg$ kubectl describe svc nginx
Name:                     nginx
Namespace:                default
Labels:                   <none>
Annotations:              external-dns.alpha.kubernetes.io/hostname=nginx.k8s.example.com.
Selector:                 app=nginx
Type:                     LoadBalancer
IP:                       100.68.183.89
LoadBalancer Ingress:     af2addc7959b811e888e41234567890-1234567890.us-east-1.elb.amazonaws.com
Port:                     http  80/TCP
TargetPort:               80/TCP
NodePort:                 http  31243/TCP
Endpoints:                100.96.2.16:80
Session Affinity:         None
External Traffic Policy:  Cluster
Events:
  Type    Reason                Age   From                Message
  ----    ------                ----  ----                -------
  Normal  EnsuringLoadBalancer  1m    service-controller  Ensuring load balancer
  Normal  EnsuredLoadBalancer   1m    service-controller  Ensured load balancer

Load Balancer url test

curl af2addc7959b811e888e41234567890-1234567890.us-east-1.elb.amazonaws.com

ExternalDNS Pod 의 로그 다시 확인

kubectl logs -f $(kubectl get po -l app=external-dns -o name)

Output:

...
time="2018-05-17T09:59:49Z" level=info msg="Desired change: CREATE nginx.k8s.example.com A"
time="2018-05-17T09:59:49Z" level=info msg="Desired change: CREATE nginx.k8s.example.com TXT"
time="2018-05-17T09:59:49Z" level=info msg="Record in zone k8s.example.com. were successfully updated"
...

로그에서 위의 메시지를 찾으면 정상적으로 Route53 에 A record가 생성 되었습니다.

잠시 후에 해당 url 에 request 를 보내봅니다.

curl nginx.k8s.example.com

Load balancer url 에 request 를 보냈을 때와 같은 결과가 나온다면 정상적으로 kubernetes cluster 설정이 완료된 것입니다.

kubernetes + Kops + ExternalDNS 정리 – 1

kubernetes + Kops + ExternalDNS 정리


AWS 서비스에서 kops를 활용해서 kubernetes cluster를 구성해보고,
AWS의 DNS 서비스인 Route53에 ExternalDNS라는 Plugin을 통해 연동해보겠습니다.

이 글은 kubernetes study를 하며 알게된 부분을 정리하는데 목적이 있습니다.

Prerequisite

  • kops 1.9.0
  • kubectl client v1.10.1, server v1.9.3
  • externalDNS **v0.5.0
  • aws-cli/1.11.69 Python/2.7.10 Darwin/17.5.0 botocore/1.5.32
  • jq v1.5

install

아래 링크대로 따라서 Kops 설치
Following this ref

Get started


이제 쿠베 설정을 해보겠습니다.

kops IAM settings


kops/aws.md at master · kubernetes/kops · GitHub
위의 링크를 참고해서 IAM 에서 해당 권한을 가진 user 생성

The kops user will require the following IAM permissions to function properly:

AmazonEC2FullAccess
AmazonRoute53FullAccess
AmazonS3FullAccess
IAMFullAccess
AmazonVPCFullAccess

User created save on local machine

로컬 aws-cli 에서 사용할 access key 와 secret key 저장

vi ~/.aws/credentials

~/.aws/credentials

[DevelopRole]
aws_access_key_id = AKIAAAAAA4FEOBBB65BA
aws_secret_access_key = aNVAAAAAACavjag5HvO7BBBbbebG/ewDefWdFicq

Domain settings


먼저 도매인이 없다면 구매를 해야합니다.
buy your own domain
구매후 약간의 시간이 필요합니다.

도매인(example.com)이 있다고 가정하고 진행합니다.
kops/aws.md at master · kubernetes/kops · GitHub

parent domain ID 확인

aws --profile DevelopRole route53 list-hosted-zones | jq '.HostedZones[] | select(.Name=="example.com.") | .Id'

Output

"/hostedzone/ZKASHKH31HAHA"

create subdomain

ID=$(uuidgen) && aws --profile DevelopRole route53 create-hosted-zone --name k8s.example.com --caller-reference $ID | \
    jq .DelegationSet.NameServers

Output

[
  "ns-1234.awsdns-17.org",
  "ns-400.awsdns-60.com",
  "ns-800.awsdns-38.net",
  "ns-1800.awsdns-46.co.uk"
]

위의 output 내용을 아래처럼 subdomain.json 파일에 저장합니다.

subdomain.json

{
  "Comment": "Create a subdomain NS record in the parent domain",
  "Changes": [
    {
      "Action": "CREATE",
      "ResourceRecordSet": {
        "Name": "k8s.example.com",
        "Type": "NS",
        "TTL": 300,
        "ResourceRecords": [
          {
            "Value": "ns-1234.awsdns-17.org"
          },
          {
            "Value": "ns-400.awsdns-60.com"
          },
          {
            "Value": "ns-800.awsdns-38.net"
          },
          {
            "Value": "ns-1800.awsdns-46.co.uk"
          }
        ]
      }
    }
  ]
}

Parent domain 에 subdomain 정보를 저장합니다.

aws --profile DevelopRole route53 change-resource-record-sets \
 --hosted-zone-id "/hostedzone/ZKASHKH31HAHA" \
 --change-batch file://subdomain.json

Output

{
    "ChangeInfo": {
        "Status": "PENDING",
        "Comment": "Create a subdomain. NS record in the parent domain",
        "SubmittedAt": "2018-05-09T08:12:45.256Z",
        "Id": "/change/C3GYMM3KUW8HAC"
    }
}

Testing your DNS setup

dig ns k8s.example.com

Output

; <<>> DiG 9.10.6 <<>> ns k8s.example.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 643
;; flags: qr rd ra; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;k8s.example.com.           IN  NS

;; ANSWER SECTION:
k8s.example.com.        300 IN  NS  ns-480.awsdns-60.com.
k8s.example.com.        300 IN  NS  ns-820.awsdns-38.net.
k8s.example.com.        300 IN  NS  ns-1909.awsdns-46.co.uk.
k8s.example.com.        300 IN  NS  ns-1162.awsdns-17.org.

;; Query time: 880 msec
;; SERVER: 10.3.21.236#53(10.3.21.236)
;; WHEN: Wed May 09 17:14:59 KST 2018
;; MSG SIZE  rcvd: 182

kops S3 settings


kops에서는 S3를 사용합니다.
S3에 저장된 데이터를 이용해서 다중 사용자가 같은 kubernetes cluster를 컨트롤할 수 있게 합니다.

Create s3 bucket for saving cluster state

clusters.k8s.example.com S3 bucket 을 생성합니다.

aws --profile DevelopRole s3 mb s3://clusters.k8s.example.com

kops/aws.md at master · kubernetes/kops · GitHub

아래 이유를 들어 kops팀에서는 versionning 설정을 추천하고 있습니다.
설정 해줍시다.

Note: We STRONGLY recommend versioning your S3 bucket in case you ever need to revert or recover a previous state store.

Versioning

aws --profile DevelopRole s3api put-bucket-versioning --bucket clusters.k8s.example.com --versioning-configuration Status=Enabled

앞으로 있을 kops 사용을 위해 아래 3가지를 environment variables 에 등록 해 줍니다.

export KOPS_STATE_STORE=s3://clusters.k8s.example.com
export AWS_PROFILE=DevelopRole
export NAME=useast1.k8s.example.com

kubernetes cluster settings by kops


이제 드디어 쿠베 클러스터에 대한 설정을 시작합니다.
위의 내용은 쿠베 클러스터 설정을 kops로 하기위한 준비과정으로 보시면 됩니다.

Create cluster configuration.

어떤 az 를 사용할 지 ,를 사용해서 선택합니다.

kops create cluster --zones="us-east-1a,us-east-1c" useast1.k8s.example.com
kops create cluster \
 --cloud=aws \
 --zones="us-east-1a" \
 --name $NAME \
 --vpc vpc-d71411b2 \
 --ssh-public-key="~/.ssh/id_rsa.pub"

Note: 아직 cluster 가 생성된 것은 아닙니다. 실제 적용을 위해서는 kops update cluster —yes 명령이 추가로 필요합니다.

Master / node check

cluster 는 마스터 노드와 마스터노드가 관리하는 여러 노드들로 구성 됩니다.
현재 구성을 아래 명령을 통해 확인 합니다.

kops get ig --name $NAME

Output

NAME            ROLE    MACHINETYPE MIN MAX ZONES
master-us-east-1a   Master  t2.small    1   1   us-east-1a
nodes           Node    t2.small    2   2   us-east-1a

노드 타입을 본인이 원하는 사이즈로 변경하고 싶을 경우, 아래 명령을 통해 가능합니다.

edit master spec

kops edit ig master-us-east-1a --name $NAME
apiVersion: kops/v1alpha2
kind: InstanceGroup
metadata:
  creationTimestamp: 2018-05-10T04:40:40Z
  labels:
    kops.k8s.io/cluster: useast1.k8s.example.com
  name: master-us-east-1a
spec:
  cloudLabels:
    Email: ktk0011+dev@gmail.com
    Owner: 9to5
    Team: devops
  image: kope.io/k8s-1.8-debian-jessie-amd64-hvm-ebs-2018-02-08
  machineType: m4.large
  maxSize: 1
  minSize: 1
  nodeLabels:
    kops.k8s.io/instancegroup: master-us-east-1a
  role: Master
  subnets:
  - us-east-1a

이때 spec 하단에 aws instance가 생성될 때 aws의 tagcloudLabels 을 통해 미리 달아줄 수 있습니다.

cloudLabels:
    Email: ktk0011+dev@gmail.com
    Owner: 9to5
    Team: devops

여기서는 instance typecloudLabels 만을 변경했습니다.

edit nodes spec

노드의 설정 또한 변경해줍니다.

kops edit ig nodes --name $NAME
apiVersion: kops/v1alpha2
kind: InstanceGroup
metadata:
  creationTimestamp: 2018-05-10T04:40:41Z
  labels:
    kops.k8s.io/cluster: useast1.k8s.example.com
  name: nodes
spec:
  cloudLabels:
    Email: ktk0011+dev@gmail.com
    Owner: 9to5
    Team: devops
  image: kope.io/k8s-1.8-debian-jessie-amd64-hvm-ebs-2018-02-08
  machineType: c4.large
  maxSize: 2
  minSize: 2
  nodeLabels:
    kops.k8s.io/instancegroup: nodes
  role: Node
  subnets:
  - us-east-1a

원하시면 cluster 구성시에 사용할 instance size 를 늘려주시면 됩니다.

Apply cluster

이제 클러스터를 실제 aws instance 에 적용해 봅시다.
kops는 dry-run을 지원하기 때문에 —yes 없이 실행하면 변경내역을 미리 확인 해볼 수 있습니다.

kops update cluster --yes $NAME

이제 kubernetes cluster 구성이 완료 되었습니다.

check cluster

Nodes 들이 잘 동작하는지 확인 해봅시다.

kubectl get nodes

Output

9to5-macbook:~/projects/k8s ktg$ kubectl get nodes
NAME                            STATUS    ROLES     AGE       VERSION
ip-10-110-39-39.ec2.internal    Ready     node      6d        v1.9.3
ip-10-110-47-235.ec2.internal   Ready     master    6d        v1.9.3
ip-10-110-63-48.ec2.internal    Ready     node      6d        v1.9.3

StatusReady 입니다. 이제 kubernetes 를 사용할 수 있습니다!!… 만!

실제 서비스를 쿠베에서 사용하려면, route53 연동은 필수 입니다. 외부에서 kubernetes 로 접근하려면, 당연히 dns 가 필요하기 때문입니다. ip는 계속 변할 수 있기 때문에…

Route53연동은 다음 글에서 계속 하겠습니다.