Kubernetes overview

What is Kubernetes?

Kubernetes is a portable, extensible open-source platform for managing containerized workloads and services, that facilitates both declarative configuration and automation.

Popular container orchestration system

Why Kubernetes?

  • Automatic binpacking (Managing container)
  • Horizontal scaling
  • Automated rollouts and rollbacks
  • Self-healing
  • Service discovery and load balancing
  • Secret and configuration management

Ref: Kubernetes & helm 활용

kubernetes component

  • Master Components
  • Node Components
  • Addons

Master Components

Master components provide the cluster’s control plane

kube-apiserver

Component on the master that exposes the Kubernetes API. It is the front-end for the Kubernetes control plane.

etcd

Consistent and highly-available key value store used as Kubernetes’ backing store for all cluster data.

kube-scheduler

Component on the master that watches newly created pods that have no node assigned, and selects a node for them to run on.

kube-controller-manager

Component on the master that runs controllers.

  • Node Controller: Responsible for noticing and responding when nodes go down.
  • Replication Controller: Responsible for maintaining the correct number of pods for every replication controller object in the system.
  • Endpoints Controller: Populates the Endpoints object (that is, joins Services & Pods).
  • Service Account & Token Controllers: Create default accounts and API access tokens for new namespaces.

cloud-controller-manager

cloud-controller-manager runs controllers that interact with the underlying cloud providers. (etc. AWS, GCP, AZURE …)

Kube api server image

Node component

kubelet

kubernetes agent on each node

check pod state(running and healthy)

kubelet is not container. -> binary file

kube-proxy

Maintaining network rules on the host and performing connection forwarding.

Container Runtime

Docker, rkt, runc, any OCI runtime-spec implementation.

Addon

Dns

Containers started by Kubernetes automatically include this DNS server in their DNS searches.

Web UI (Dashboard)

Kubernetes Architecture

kubrnetes architecture – 1
kubernetes architecture – 2

Kubernetes API

The Kubernetes API also serves as the foundation for the declarative configuration schema for the system.
The kubectl command-line tool can be used to create, update, delete, and get API objects.

OpenAPI and Swagger definitions

/openapi/v2

To make it easier to eliminate fields or restructure resource representations, Kubernetes supports multiple API versions, each at a different API path, such as /api/v1 or /apis/extensions/v1beta1.

API groups

  1. The core group, often referred to as the legacy group, is at the REST path /api/v1 and uses apiVersion: v1.
  2. The named groups are at REST path /apis/$GROUP_NAME/$VERSION, and use apiVersion: $GROUP_NAME/$VERSION (e.g. apiVersion: batch/v1)
apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: dreg.be/tkwon/nginx-test:latest
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: nginx
spec:
  replicas: 1
  template:
    spec:
      containers:
      - name: nginx
        image: nginx:alpine
        ports:
        - containerPort: 80

API versioning

The Kubernetes API – Kubernetes

Just Only need remember this one.

Use api beta and stable

Kubernetes Object Management

The kubectl command-line tool

kubectl run nginx --image nginx

or

kubectl create deployment nginx --image nginx

or more important object

kubectl apply -f nginx.yaml

Pod

  • A Pod is the basic building block of Kubernetes
  • the smallest and simplest unit
  • Represents a unit of deployment
  • Pods that run a single container.
  • Pods that run multiple containers that need to work together.

Example for multiple containers in the Pod

The specific instances in which your containers are tightly coupled.

Multi Pod structure

Pod detail

Pods provide two kinds of shared resources for their constituent containers: networking and storage.

Containers inside a Pod can communicate with one another using localhost

Pods and Controllers

A Controller can create and manage multiple Pods

  • Deployment
  • StatefulSet
  • DaemonSet

Controllers

Deployment

To provides Declarative updates for Pods and ReplicaSets

deployment, replica set and pod

deployment yaml

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

Statefulset

The workload API object used to manage stateful applications

To provides guarantees about the ordering and uniqueness of these Pods.

Using Statefulset

  • Stable, unique network identifiers.
  • Stable, persistent storage.
  • Ordered, graceful deployment and scaling.
  • Ordered, automated rolling updates.

Limitations

Deleting and/or scaling a StatefulSet down will not delete the volumes associated with the StatefulSet.

Pod Identity

StatefulSet Pods have a unique identity
The identity sticks to the Pod, regardless of which node it’s (re)scheduled on.

$(statefulset name)-$(ordinal). The example above will create three Pods named web-0,web-1,web-2

DaemonSet

A DaemonSet ensures that all (or some) Nodes run a copy of a Pod.

ex> Node exporter for prometheus

Services and Network

Service

Expose Pods and can make commnunication between Pods.

kind: Service
apiVersion: v1
metadata:
  name: my-service
spec:
  selector:
    app: MyApp
  ports:
  - protocol: TCP
    port: 80
    targetPort: 9376

my-service.my-namespace.svc.cluster.local

  • ClusterIP
  • NodePort
  • LoadBalancer
  • Match ELB on AWS
  • ExternalName

Ingress

Similar wih Service, but
Service is kind of L4 network
Ingress is kind of L7 network.

Service map to AWS ELB,
Ingress map to AWS ALB

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: test-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - http:
      paths:
      - path: /testpath
        backend:
          serviceName: test
          servicePort: 80

Storage

  • Volumes
  • Persistent Volumes
  • PersistentVolumeClaim
  • Storage Classes

Configuration

  • Secrets
  • ConfigMap

Kubernetes Pod Network

Pod network

I think network is so importance to understand kubernetes.
You should check below references to understand it.

Network references:
understanding-kubernetes-networking-pods
Container Networking From Scratch
About Linux network namespace

kubernetes logging fluent-bit deamonset

여기서는 fluent-bit deamonset 설정하면서 만난 문제점을 간단히 공유하고자 합니다.

fluent-bit deamonset 설정

GitHub – fluent/fluent-bit-kubernetes-logging: Fluent Bit Kubernetes Daemonset

위 리포지토리를 참고해서 fluentbit 을 설치합니다.

rbac 설정

먼저 RBAC for fluentbit 을 설정합니다.0

kubectl create -f https://raw.githubusercontent.com/fluent/fluent-bit-kubernetes-logging/master/fluent-bit-service-account.yaml
$ kubectl create -f https://raw.githubusercontent.com/fluent/fluent-bit-kubernetes-logging/master/fluent-bit-role.yaml
$ kubectl create -f https://raw.githubusercontent.com/fluent/fluent-bit-kubernetes-logging/master/fluent-bit-role-binding.yaml

원하는 logging system 선택(elastic search)

로그를 보내고 싶은 저장소를 선택합니다. 여기서는 elasticsearch.

  • Fluent bit configmap 생성
 kubectl create -f https://raw.githubusercontent.com/fluent/fluent-bit-kubernetes-logging/master/output/elasticsearch/fluent-bit-configmap.yaml
  • Fluent bit elastic search Daemonset 생성

0FLUENT_ELASTICSEARCH_HOSTFLUENT_ELASTICSEARCH_HOST 값을 각자의 시스템에 맞게 변경하고 배포합니다.

kubectl create -f https://raw.githubusercontent.com/fluent/fluent-bit-kubernetes-logging/master/output/elasticsearch/fluent-bit-ds.yaml

그리고 각 데몬셋의 로그를 확인합니다.!!

그러면 elasticsearch에 로그를 보낼 때마다 에러 발생….

에러

apiVersion: v1
kind: ConfigMap
metadata:
  name: fluent-bit-config
  namespace: logging
  labels:
    k8s-app: fluent-bit
data:
  # Configuration files: server, input, filters and output
  # ======================================================
  fluent-bit.conf: |
    [SERVICE]
        Flush         1
        Log_Level     info
        Daemon        off
        Parsers_File  parsers.conf
        HTTP_Server   On
        HTTP_Listen   0.0.0.0
        HTTP_Port     2020

    @INCLUDE input-kubernetes.conf
    @INCLUDE filter-kubernetes.conf
    @INCLUDE output-elasticsearch.conf

  input-kubernetes.conf: |
    [INPUT]
        Name              tail
        Tag               kube.*
        Path              /var/log/containers/*.log
        Parser            docker
        DB                /var/log/flb_kube.db
        Mem_Buf_Limit     5MB
        Skip_Long_Lines   On
        Refresh_Interval  10

  filter-kubernetes.conf: |
    [FILTER]
        Name                kubernetes
        Match               kube.*
        Kube_URL            https://kubernetes.default.svc.cluster.local:443
        Merge_Log           On
        K8S-Logging.Parser  On

  output-elasticsearch.conf: |
    [OUTPUT]
        Name            es
        Match           *
        Host            ${FLUENT_ELASTICSEARCH_HOST}
        Port            ${FLUENT_ELASTICSEARCH_PORT}
        Logstash_Format On
        Retry_Limit     False

  parsers.conf: |
    [PARSER]
        Name   apache
        Format regex
        Regex  ^(?<host>[^ ]*) [^ ]* (?<user>[^ ]*) \[(?<time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^\"]*?)(?: +\S*)?)?" (?<code>[^ ]*) (?<size>[^ ]*)(?: "(?<referer>[^\"]*)" "(?<agent>[^\"]*)")?$
        Time_Key time
        Time_Format %d/%b/%Y:%H:%M:%S %z

    [PARSER]
        Name   apache2
        Format regex
        Regex  ^(?<host>[^ ]*) [^ ]* (?<user>[^ ]*) \[(?<time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^ ]*) +\S*)?" (?<code>[^ ]*) (?<size>[^ ]*)(?: "(?<referer>[^\"]*)" "(?<agent>[^\"]*)")?$
        Time_Key time
        Time_Format %d/%b/%Y:%H:%M:%S %z

    [PARSER]
        Name   apache_error
        Format regex
        Regex  ^\[[^ ]* (?<time>[^\]]*)\] \[(?<level>[^\]]*)\](?: \[pid (?<pid>[^\]]*)\])?( \[client (?<client>[^\]]*)\])? (?<message>.*)$

    [PARSER]
        Name   nginx
        Format regex
        Regex ^(?<remote>[^ ]*) (?<host>[^ ]*) (?<user>[^ ]*) \[(?<time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^\"]*?)(?: +\S*)?)?" (?<code>[^ ]*) (?<size>[^ ]*)(?: "(?<referer>[^\"]*)" "(?<agent>[^\"]*)")?$
        Time_Key time
        Time_Format %d/%b/%Y:%H:%M:%S %z

    [PARSER]
        Name   json
        Format json
        Time_Key time
        Time_Format %d/%b/%Y:%H:%M:%S %z

    [PARSER]
        Name        docker
        Format      json
        Time_Key    time
        Time_Format %Y-%m-%dT%H:%M:%S.%L
        Time_Keep   On
        # Command      |  Decoder | Field | Optional Action
        # =============|==================|=================
        Decode_Field_As   escaped    log

    [PARSER]
        Name        syslog
        Format      regex
        Regex       ^\<(?<pri>[0-9]+)\>(?<time>[^ ]* {1,2}[^ ]* [^ ]*) (?<host>[^ ]*) (?<ident>[a-zA-Z0-9_\/\.\-]*)(?:\[(?<pid>[0-9]+)\])?(?:[^\:]*\:)? *(?<message>.*)$
        Time_Key    time
        Time_Format %b %d %H:%M:%S

위에가 fluent bit의 기본 config 내용입니다.

기본적으로 kubernetes 에 pod 를 run 한다는 의미는 docker container 를 실행한다는 의미입니다.

그 말인 즉, docker log 는 항상 발생한다는 이야기…

제가 본 에러는 elasticsearch 의 timestamp
PARSER 부분의 docker 설정에서 parsing 되는 timestamp와의 충돌에서 생기는 문제였습니다.

[PARSER]
        Name        docker
...
        Time_Keep   Off
...

위 부분의 Time_KeepOn 에서 Off 로 변경해주고 다시 Daemonset을 실행합니다.

kubectl apply -f fluent-bit-configmap.yaml
kubectl delete -f fluent-bit-ds.yaml
kubectl apply -f fluent-bit-ds.yaml

이제 정상적으로 ES로 로그가 보내지는 것을 확인합니다.

실행 확인

pod의 로그를 봐서 fluent-bit 의 정상 동작 여부를 확인해도 되지만,

참고로 prometheus 가 kubernetes cluster 에 이미 설정되어 있다면,

annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "2020"
        prometheus.io/path: /api/v1/metrics/prometheus

데몬셋의 위의 annotations 설정으로 prometheus가 자동으로 fluent-bit 타켓을 찾아서 fluent-bit 의 메트릭을 수집합니다.

따라서, prometheus dashboard 에서 fluentbit_output_errors_total 이 메트릭을 확인하면 error 가 나고 있는지 아닌지 간단히 확인 가능합니다.

Docker private registry 간편 설정 KOPS

이 전 글에서 kubernetes 에 docker private registry 를 사용하는 방법 에 대해 기술 했습니다.

하지만 막상 사용하다보면 매번 imagePullSecrets 을 입력해줘야하는 불편함이 있습니다.

해서 각 노드마다 docker.json 을 배포해주면 되지 않을까 싶어서 찾아보다보니 kops 에서 자체적으로 지원합니다.

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

위 링크를 가면 아래와 같은 방법이 나옵니다만…

kops create secret --name <clustername> dockerconfig -f ~/.docker/config.json
kops rolling-update cluster --name <clustername> --yes

제 현재 상황에서는 rolling-update 를 실행해도 변화된것이 없다고 적용이 되지 않더군요.

현재 버전

KOPS Version: 1.10.0 (git-8b52ea6d1)
kubernetesVersion: 1.10.6

방법은 간단합니다. 강제로 rolling-update를 수행하면 됩니다.

kops rolling-update cluster --name <clustername> --yes --force

이제 손쉽게 docker private registry를 사용할 수 있습니다.

Kubernetes + private docker registry

kubernetes 설정이 완료 되었으면, 이제 실제로 kubernetes 를 사용하기 위한 docker registry 연동 설정을 해보겠습니다.

보통 회사에서 사용할 때는 자체 private docker registry 를 사용하고 있을 겁니다.

이 registry 를 kubectl 을 이용해서 연동해봅시다.

Set up Docker Registry

Pull an Image from a Private Registry – Kubernetes
위 글을 참고 하여 연동을 합니다.

등록

Usage:

kubectl create secret docker-registry NAME –docker-username=user –docker-password=password –docker-email=email [–docker-server=string] [–from-literal=key1=value1] [–dry-run] [options]

kubectl create secret docker-registry regcred --docker-server=https://example.com --docker-username=aws --docker-password="abcd1234" --docker-email="aws@example.com"

Docker-registry secret 이름을 여기서는 regcred로 생성해서 만들었습니다.

확인

kubectl get secret regcred --output=yaml

Output:

apiVersion: v1
data:
  .dockerconfigjson: eyJhdXRocyI6eyJodH3FovL2RyZWcuYcyI6eyJodH3FovMiLCJwYXNzd29yZCI6ImdyYWNlbm90ZTEyIUAiLC3D2KWFpbCI6ImF3c0BncmFjZW5vdGUuY29tIiwiYXV0aCI6IllYZHpPbWR5WVdObGJcyI6eyJodH3FovJ9fX0=
kind: Secret
metadata:
  creationTimestamp: 2018-07-11T06:15:56Z
  name: regcred
  namespace: default
  resourceVersion: "6036405"
  selfLink: /api/v1/namespaces/default/secrets/regcred
  uid: df5d178b-84d1-11e8-b74a-0e9d5423172c
type: kubernetes.io/dockerconfigjson

출력 결과에서 .dockerconfigjosn이라는 내용은 실제 docker registry 접속정보를 나타냅니다. 아래 명령을 통해 정상적으로 접속정보가 들어갔는지 확인 해 볼 수 있습니다.

kubectl get secret regcred --output="jsonpath={.data.\.dockerconfigjson}" | base64 -d

맥북일 경우는 -d 옵션을 대문자  -D 로 변경합니다.  

kubectl get secret regcred --output="jsonpath={.data.\.dockerconfigjson}" | base64 -D

Output:

{"auths":{"https://example.com":{"username":"aws","password":"abcd1234","email":"aws@example.com","auth":"YxDAOmdyKZNlbm30ZTEyIUA="}}}

TEST

이제 설정은 완료됐습니다.
실제 pod 를 배포해서 정상적으로 동작하는지 TEST 해 봅시다.

apiVersion: v1
kind: Pod
metadata:
  name: private-reg
spec:
  containers:
  - name: private-reg-container-nginx
    image: example.be/devops/nginx-custom:0.1.0
  imagePullSecrets:
  - name: regcred

위의 내용으로 my-private-reg-pod.yml 파일을 생성합니다.

kubectl create -f my-private-reg-pod.yml

위 명령 실행후 정상적으로 pod 생성이 되는지 확인합니다.

kubectl get po private-reg

Output:

 ✘ ktg@dev-vm-of00-016  ~/projects/k8s kubectl get po private-reg
NAME                            READY     STATUS    RESTARTS   AGE
private-reg                     1/1       Running   0          1m

STATUS 가 Running 이면 정상동작하는 것을 알수 있습니다.

curl로 nginx 작동 여부를 확인하고 싶다면,

kubectl exec -it private-reg (bash or sh)
curl localhost

위 명령을 실행 하면 됩니다.

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연동은 다음 글에서 계속 하겠습니다.

Kubernetes on AWS + kops

Kubernetes on AWS + kops

Prerequisite

  • kops 1.9.0
  • kubectl client v1.10.1, server v1.9.3

설치

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

그리고 어김없이 찾아오는 에러…

Error

kops 로 클러스터 구성(after executing, kops update cluster —yes $NAME) 후에 아래와 같은 output 을 볼수 있습니다.

...

Suggestions:
 * validate cluster: kops validate cluster
 * list nodes: kubectl get nodes --show-labels
 * ssh to the master: ssh -i ~/.ssh/id_rsa admin@api.useast1.k8s.example.com
 * the admin user is specific to Debian. If not using Debian please use the appropriate user based on your OS.
 * read about installing addons at: https://github.com/kubernetes/kops/blob/master/docs/addons.md.

아래 명령을 실행합니다.

kops validate cluster

Output:

tkwon-macbook:~/projects/k8s ktg$ kops validate cluster
Using cluster from kubectl context: useast1.k8s.example.com

Validating cluster useast1.k8s.example.com

INSTANCE GROUPS
NAME            ROLE    MACHINETYPE MIN MAX SUBNETS
master-us-east-1a   Master  m5.large    1   1   us-east-1a
nodes           Node    c5.large    2   2   us-east-1a

NODE STATUS
NAME    ROLE    READY

VALIDATION ERRORS
KIND    NAME        MESSAGE
dns apiserver   Validation Failed

The dns-controller Kubernetes deployment has not updated the Kubernetes cluster's API DNS entry to the correct IP address.  The API DNS IP address is the placeholder address that kops creates: 203.0.113.123.  Please wait about 5-10 minutes for a master to start, dns-controller to launch, and DNS to propagate.  The protokube container and dns-controller deployment logs may contain more diagnostic information.  Etcd and the API DNS entries must be updated for a kops Kubernetes cluster to start.

Validation Failed

뭔지 모르겠지만 실패했습니다.
ssh로 master node 에 접속 시도 해봅니다.

ssh -i ~/.ssh/id_rsa admin@api.useast1.k8s.example.com

ssh 접속이 되지 않았습니다. ..

route53에서 확인해보니 도매인의 ip가 master node ip로 변경되어 있지 않았습니다.

Ec2 dashboard 를 확인하니 정상적으로 instance가 실행중입니다.

ip로 ssh 접속 시도합니다.

ssh -i ~/.ssh/id_rsa admin@1.1.1.1

정상적으로 접속됩니다.

sudo docker ps

Output:

protokube                                          1.9.0

protokube 라는 이미지가 실행중입니다.
docker log 를 확인해보니 아래와 같은 에러로그가 반복되고 있습니다.

...
Mar 30 22:30:38 ip-172-31-1-13 docker[1546]: I0330 22:30:38.659365    1576 aws_volume.go:318] nvme path not found "/rootfs/dev/disk/by-id/nvme-Amazon_Elastic_Block_Store_vol0f1a04a36c6baaaae"
Mar 30 22:30:38 ip-172-31-1-13 docker[1546]: I0330 22:30:38.659373    1576 volume_mounter.go:107] Waiting for volume "vol-0f1a04a36c6baaaae" to be attached
Mar 30 22:30:39 ip-172-31-1-13 docker[1546]: I0330 22:30:39.659499    1576 aws_volume.go:318] nvme path not found "/rootfs/dev/disk/by-id/nvme-Amazon_Elastic_Block_Store_vol0f1a04a36c6baaaae"
Mar 30 22:30:39 ip-172-31-1-13 docker[1546]: I0330 22:30:39.659519    1576 volume_mounter.go:107] Waiting for volume "vol-0f1a04a36c6baaaae" to be attached
Mar 30 22:30:40 ip-172-31-1-13 docker[1546]: I0330 22:30:40.659641    1576 aws_volume.go:318] nvme path not found "/rootfs/dev/disk/by-id/nvme-Amazon_Elastic_Block_Store_vol0f1a04a36c6baaaae"
Mar 30 22:30:40 ip-172-31-1-13 docker[1546]: I0330 22:30:40.659660    1576 volume_mounter.go:107] Waiting for volume "vol-0f1a04a36c6baaaae" to be attached
...

검색해보니,
Cluster hung up starting, waiting for etcd volumes to attach · Issue #4844 · kubernetes/kops · GitHub

위의 링크에 원인이 나와있었습니다.
thumb up 한번 눌러드리고 수정합니다.

$ kops edit ig master-us-east-1a
$ kops edit ig nodes

위 명령을 실행해서 instance type m5, c5 에서 m4, c4로 바꿉니다.

kops rolling-update cluster --cloudonly --yes

실행하고… 기다립니다.

kops validate cluster

Output:

tkwon-macbook:~/projects/k8s ktg$ kops validate cluster
Using cluster from kubectl context: useast1.k8s.example.com

Validating cluster useast1.k8s.example.com

INSTANCE GROUPS
NAME            ROLE    MACHINETYPE MIN MAX SUBNETS
master-us-east-1a   Master  m4.large    1   1   us-east-1a
nodes           Node    c4.large    2   2   us-east-1a

NODE STATUS
NAME                ROLE    READY
ip-10-110-42-179.ec2.internal   node    True
ip-10-110-45-25.ec2.internal    master  True
ip-10-110-47-243.ec2.internal   node    True

Your cluster useast1.k8s.example.com is ready

이제 kubernetes cluster 를 사용할 수 있습니다!