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

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

Opsworks agent error debugging

어느날 Prometheus alert이 와서 Portus 에 문제가 생겼음을 알렸습니다.

Prerequisite

  • AWS Opsworks chef12
  • ubuntu 16.04

디버깅

Disk Full

확인해보니 문제는

no space ….

이런 에러…

$ df -h 

로 보니 root가 disk full 입니다.

확인해보니 portus의 nginx 로그가 2GB 씩 stdout, stderr 두개….

로그 지우고 nginx 재시작으로 임시 해결..

AWS opsworks 의 agent 가 업데이트 될때 마침 디스크가 꽉 차있었나 봅니다…

용량 확보 후 재시작…

opsworks-agent-cli를 수동으로 실행해서 configure를 다시 실행시키는 것을 시도 합니다….

root@portus2:/var/lib/aws/opsworks/chef# opsworks-agent-cli run_command configure

결과는 에러…

Couldn't execute run_command: RuntimeError - Couldn't gather commands. RuntimeError - Could not parse /var/lib/aws/opsworks/chef/2018-08-28-10-28-23-01.json: JSON::ParserError - A JSON text must at least contain two octets! - /opt/aws/opsworks/current/vendor/bundle/ruby/2.2.0/gems/json-1.8.3/lib/json/common.rb:155:in `initialize'
/opt/aws/opsworks/current/vendor/bundle/ruby/2.2.0/gems/json-1.8.3/lib/json/common.rb:155:in `new'
/opt/aws/opsworks/current/vendor/bundle/ruby/2.2.0/gems/json-1.8.3/lib/json/common.rb:155:in `parse'
/opt/aws/opsworks/current/lib/cli/base.rb:69:in `parse_json'
/opt/aws/opsworks/current/lib/cli/base.rb:17:in `block in gather_commands'
/opt/aws/opsworks/current/lib/cli/base.rb:16:in `each'
/opt/aws/opsworks/current/lib/cli/base.rb:16:in `gather_commands'
/opt/aws/opsworks/current/lib/cli/runner.rb:72:in `initialize'
/opt/aws/opsworks/current/lib/cli/runner.rb:50:in `new'
/opt/aws/opsworks/current/lib/cli/runner.rb:50:in `run'
/usr/sbin/opsworks-agent-cli:91:in `block (2 levels) in <main>'
/opt/aws/opsworks/current/vendor/bundle/ruby/2.2.0/gems/gli-2.13.4/lib/gli/command_support.rb:126:in `call'
/opt/aws/opsworks/current/vendor/bundle/ruby/2.2.0/gems/gli-2.13.4/lib/gli/command_support.rb:126:in `execute'
/opt/aws/opsworks/current/vendor/bundle/ruby/2.2.0/gems/gli-2.13.4/lib/gli/app_support.rb:296:in `block in call_command'
/opt/aws/opsworks/current/vendor/bundle/ruby/2.2.0/gems/gli-2.13.4/lib/gli/app_support.rb:309:in `call'
/opt/aws/opsworks/current/vendor/bundle/ruby/2.2.0/gems/gli-2.13.4/lib/gli/app_support.rb:309:in `call_command'
/opt/aws/opsworks/current/vendor/bundle/ruby/2.2.0/gems/gli-2.13.4/lib/gli/app_support.rb:83:in `run'
/usr/sbin/opsworks-agent-cli:110:in `<main>' - /opt/aws/opsworks/current/lib/cli/base.rb:71:in `rescue in parse_json'
/opt/aws/opsworks/current/lib/cli/base.rb:69:in `parse_json'
/opt/aws/opsworks/current/lib/cli/base.rb:17:in `block in gather_commands'
/opt/aws/opsworks/current/lib/cli/base.rb:16:in `each'
/opt/aws/opsworks/current/lib/cli/base.rb:16:in `gather_commands'
/opt/aws/opsworks/current/lib/cli/runner.rb:72:in `initialize'
/opt/aws/opsworks/current/lib/cli/runner.rb:50:in `new'
/opt/aws/opsworks/current/lib/cli/runner.rb:50:in `run'
/usr/sbin/opsworks-agent-cli:91:in `block (2 levels) in <main>'
/opt/aws/opsworks/current/vendor/bundle/ruby/2.2.0/gems/gli-2.13.4/lib/gli/command_support.rb:126:in `call'
/opt/aws/opsworks/current/vendor/bundle/ruby/2.2.0/gems/gli-2.13.4/lib/gli/command_support.rb:126:in `execute'
/opt/aws/opsworks/current/vendor/bundle/ruby/2.2.0/gems/gli-2.13.4/lib/gli/app_support.rb:296:in `block in call_command'
/opt/aws/opsworks/current/vendor/bundle/ruby/2.2.0/gems/gli-2.13.4/lib/gli/app_support.rb:309:in `call'
/opt/aws/opsworks/current/vendor/bundle/ruby/2.2.0/gems/gli-2.13.4/lib/gli/app_support.rb:309:in `call_command'
/opt/aws/opsworks/current/vendor/bundle/ruby/2.2.0/gems/gli-2.13.4/lib/gli/app_support.rb:83:in `run'
/usr/sbin/opsworks-agent-cli:110:in `<main>' - /opt/aws/opsworks/current/lib/cli/base.rb:33:in `rescue in gather_commands'
/opt/aws/opsworks/current/lib/cli/base.rb:15:in `gather_commands'
/opt/aws/opsworks/current/lib/cli/runner.rb:72:in `initialize'
/opt/aws/opsworks/current/lib/cli/runner.rb:50:in `new'
/opt/aws/opsworks/current/lib/cli/runner.rb:50:in `run'
/usr/sbin/opsworks-agent-cli:91:in `block (2 levels) in <main>'
/opt/aws/opsworks/current/vendor/bundle/ruby/2.2.0/gems/gli-2.13.4/lib/gli/command_support.rb:126:in `call'
/opt/aws/opsworks/current/vendor/bundle/ruby/2.2.0/gems/gli-2.13.4/lib/gli/command_support.rb:126:in `execute'
/opt/aws/opsworks/current/vendor/bundle/ruby/2.2.0/gems/gli-2.13.4/lib/gli/app_support.rb:296:in `block in call_command'
/opt/aws/opsworks/current/vendor/bundle/ruby/2.2.0/gems/gli-2.13.4/lib/gli/app_support.rb:309:in `call'
/opt/aws/opsworks/current/vendor/bundle/ruby/2.2.0/gems/gli-2.13.4/lib/gli/app_support.rb:309:in `call_command'
/opt/aws/opsworks/current/vendor/bundle/ruby/2.2.0/gems/gli-2.13.4/lib/gli/app_support.rb:83:in `run'
/usr/sbin/opsworks-agent-cli:110:in `<main>'

File check

파싱이 안된다고 합니다. 파일을 오픈 합니다.

$ vi /var/lib/aws/opsworks/chef/2018-08-28-10-28-23-01.json

파일이 비어있습니다… 이상합니다…

JSON 파일은 opsworks 의 리소스들의 정보가 모여있는 파일입니다. 파일 내용이 있어야하는데…. 아마도 용량문제로 파일을 쓰지 못한 듯 합니다.

/var/lib/aws/opsworks/chef 이 폴더로 들어가서 뭐하는 폴더인지 확인합니다.

Directory check

$ ll
root@portus2:/var/lib/aws/opsworks/chef# ll
total 2168
drwxr-xr-x 2 aws  aws    4096 Aug 29 11:28 ./
drwxr-x--- 6 aws  aws    4096 Aug 29 11:21 ../
-rw-r--r-- 1 aws  aws       0 May 10 03:44 2018-05-08-10-10-48-01.log.gz
-rw-r--r-- 1 aws  aws  102226 Jul 17 09:55 2018-07-17-09-55-08-01.log
-rw-r--r-- 1 aws  aws       0 Aug 28 10:24 2018-08-28-10-24-52-01.json
-rw-r--r-- 1 aws  aws       0 Aug 28 10:28 2018-08-28-10-28-23-01.json
-rw-r--r-- 1 aws  aws       0 Aug 28 16:20 2018-08-28-16-20-30-01.json
-rw-r--r-- 1 aws  aws  157625 Aug 28 16:31 2018-08-28-16-31-31-01.json
-rw-r--r-- 1 root root 105741 Aug 28 16:31 2018-08-28-16-31-31-01.log
-rw-r--r-- 1 aws  aws  157559 Aug 29 05:55. 2018-08-29-05-55-37-01.json
-rw-r--r-- 1 root root 102946 Aug 29 05:55 2018-08-29-05-55-37-01.log
-rw-r--r-- 1 aws  aws  157560 Aug 29 06:14 2018-08-29-06-14-17-01.json
-rw-r--r-- 1 root root 103341 Aug 29 06:14 2018-08-29-06-14-17-01.log
-rw-r--r-- 1 aws  aws  149311 Aug 29 11:04 2018-08-29-11-04-15-01.json
-rw-r--r-- 1 root root  98073 Aug 29 11:04 2018-08-29-11-04-15-01.log
-rw-r--r-- 1 aws  aws  157559 Aug 29 11:07 2018-08-29-11-07-52-01.json
-rw-r--r-- 1 root root 103340 Aug 29 11:07 2018-08-29-11-07-52-01.log
-rw-r--r-- 1 aws  aws  157558 Aug 29 11:11 2018-08-29-11-11-30-01.json
-rw-r--r-- 1 root root 103651 Aug 29 11:11 2018-08-29-11-11-30-01.log
-rw-r--r-- 1 aws  aws  157656 Aug 29 11:21 2018-08-29-11-21-38-01.json
-rw-r--r-- 1 root root 103648 Aug 29 11:21 2018-08-29-11-21-38-01.log
-rw-r--r-- 1 aws  aws  157670 Aug 29 11:24 2018-08-29-11-24-45-01.json
-rw-r--r-- 1 root root 100655 Aug 29 11:24 2018-08-29-11-24-45-01.log

빈 파일이 몇개 있습니다..

Removing empty files

뭔지 모르겠지만 비어서 parsing error 가 나는 듯 합니다.
과감히 빈 파일을 지워줍니다.

$> rm 2018-08-28-10-24-52-01.json

그리고 다시 위의 명령을 실행합니다.

뭔가 바뀔까요?

root@portus2:/var/lib/aws/opsworks/chef# opsworks-agent-cli run_command configure
Couldn't execute run_command: RuntimeError - Couldn't gather commands. RuntimeError - Could not parse /var/lib/aws/opsworks/chef/2018-08-28-10-28-23-01.json: JSON::ParserError - A JSON text must at least contain two octets! - /opt/aws/opsworks/current/vendor/bundle/ruby/2.2.0/gems/json-1.8.3/lib/json/common.rb:155:in `initialize'
...

그 뒤의 파일을 읽기 시도하다가 에러가 다시 발생했습니다.
이제 이상한 json 파일들을 전부 지워줍시다.

그리고 다시 agent 를 실행하면 정상동작하는 것을 볼 수 있습니다.

[2018-08-29T11:50:10+00:00] INFO: Processing directory[/etc/logrotate.d] action create (/var/chef/runs/5f5d34e2-e02f-4871-98b9-c842712610de/local-mode-cache/cache/cookbooks/logrotate/resources/app.rb line 60)
[2018-08-29T11:50:10+00:00] INFO: Processing template[/etc/logrotate.d/nginx] action create (/var/chef/runs/5f5d34e2-e02f-4871-98b9-c842712610de/local-mode-cache/cache/cookbooks/logrotate/resources/app.rb line 67)
[2018-08-29T11:50:10+00:00] INFO: HTTP Request Returned 404 Not Found: Object not found: chefzero://localhost:8889/nodes/portus2
[2018-08-29T11:50:10+00:00] INFO: Chef Run complete in 0.286619869 seconds
[2018-08-29T11:50:10+00:00] INFO: Running report handlers
[2018-08-29T11:50:10+00:00] INFO: Report handlers complete
[2018-08-29 11:50:10]  INFO [opsworks-agent(16252)]: Finished Chef run with exitcode 0

chef12 opsworks 실행

Chef-client 를 수동으로 실행하려면 아래와 같은 명령을 사용하면 가능합니다.

opsworks-agent-cli 실행 시 확인 가능

  1. 첫번째 chef-client 실행
# RUBYOPT="-E utf-8" /opt/aws/opsworks/current/bin/chef-client -j /var/lib/aws/opsworks/chef/2018-08-30-05-10-55-01.json -c /var/lib/aws/opsworks/client.internal.rb -o aws_opsworks_agent 2>&1
  1. 두번째 chef-client 실행
# RUBYOPT="-E utf-8" /opt/chef/bin/chef-client -j /var/chef/runs/fd1800b7-5b1e-4ec8-b7dd-3579f6c568c3/attribs.json -c /var/chef/runs/fd1800b7-5b1e-4ec8-b7dd-3579f6c568c3/client.rb  2>&1

위의 attribs.json 파일을 살펴보니…

{
  "region_project": "TEST",
  "region": "TEST01",
  ...
  ...
  "run_list": [
    "recipe[logrotate::nginx]"
  ]
}

run_list 로 제가 OpsWorks Stack 에서 execute_recipes 한 내용이 기록되어 있습니다.
logrotate 레시피를 추가해줬죠…

attribs.json 파일에 현재 실행할 명령들이 들어가는 구조로 보입니다.
필요하다면 run_list 에 명령을 변경해서 실행한다면 가능할 듯….

이상 opsworks-agent-cli 관련 디버깅 경험 이슈였습니다.

iOS app preview 동영상 생성 방법

iOS의 앱 미리보기 (app preview) 동영상(video) 를 올리고 싶을 경우,
동영상 편집을 해야하는데 상당히 번거롭습니다.

간단히 만드는 법을 여기 기록 합니다.

화면 기록(실제 디바이스)

실제 디바이스에서 화면기록을 이용합니다.

설정 > 제어 센터 > 제어 항목 사용자화
위의 메뉴로 들어가서 화면 기록 을 위로 올려서 손전등, 카메라 등이 있는 빠른 메뉴에 화면기록을 추가합니다.

디바이스가 없을 경우

실제 디바이스가 없는 경우, 저는 iPad가 없는데 iPad의 동영상을 만들고 싶은 경우 입니다.

이때는 시뮬레이터를 실행 하고, 아래 명령을 실행합니다.

xcrun simctl io booted recordVideo app-preview-video.mov

화면 녹화 후에 는 ctrl+c 로 종료합니다.

편집

동영상 원본이 만들어졌다면 이제 편집이 필요합니다.
전 빠르게 재생하고 싶었습니다.

iMovie 를 이용합니다.

App Store 에서 iMovie 를 설치하고 실행합니다.

파일 > 새로운 앱 미리보기를 선택해서 새로운 app preview 프로젝트를 생성합니다.

프로젝트 생성 후 비디오 동영상 원본소스를 추가합니다.

하단의 창에 동영상 원본소스를 끌어다 놓습니다.

중요
오디오 파일을 아무거나 추가하고 하단의 창에 오디오 소스를 끌어다 놓습니다.

우상단의 아이콘중 계기판 표시를 선택해서 원하는 재생 스피디를 결정합니다.
또 소리를 선택해서 소리를 0% 로 변경합니다.
만약 본인의 음악파일이 저작권이 있다면 볼륨을 올리셔도 무방합니다.

편집이 완료되었다면, 우상단의 공유 버튼을 누르고, 앱 미리보기 를 선택해서 파일로 저장합니다.

이제 앱스토에서 동영상을 올리기만 하면 됩니다.

golang migrate 사용시 에러

필요성

예전 golang 코드에 사용하고 있는 db migration 관리 라이브러리: migrate이 라이브러리는 기존 레포에서,
GitHub – mattes/migrate: Database migrations. CLI and Golang library. ->
아래 레포로 리포지토리가 변경되어 관리 중에 있었습니다.
GitHub – golang-migrate/migrate: Database migrations. CLI and Golang library.

기존 코드 수정이 필요해서 README 를 읽기 위해 들어왔다가 기존 코드가 예전과는 다르게 변경, 관리되는 것을 알게되어서 레포 변경을 하기로 마음을 먹었습니다.

문제

migrate up


변경 후 migrate up 실행시에 아래와 같은 문제가 발생했습니다.

error: pq: column “dirty” does not exist in line 0 · Issue #34 · golang-migrate/migrate · GitHub

아마 dirty 컬럼이 추가 된 듯 합니다.
dirty column 을 수동으로 추가해줍니다.

alter table schema_migrations add column dirty boolean not null default false;

다시 migrate 를 실행하면 정상동작합니다.

Postgres driver 를 사용할때 발생하는 문제


  • Postgres driver 를 사용할때 발생하는 문제
  • Migrate 라이브러리 import 후에 New, Up 코드 작성
  • Compile and Run
  • have panic….
panic: sql: Register called twice for driver postgres

goroutine 1 [running]:
database/sql.Register(0x1f34e5d, 0x8, 0x2794580, 0x281b590)
        /Users/ktg/.gvm/gos/go1.9/src/database/sql/sql.go:50 +0x1ad
github.com/golang-migrate/migrate/vendor/github.com/lib/pq.init.0()
        /Users/ktg/go/src/github.com/golang-migrate/migrate/vendor/github.com/lib/pq/conn.go:49 +0x5c
github.com/golang-migrate/migrate/vendor/github.com/lib/pq.init()
        <autogenerated>:1 +0x782
github.com/golang-migrate/migrate/database/postgres.init()
        <autogenerated>:1 +0x7d
devops-mocha3/cmd/mocha/setup.init()
        <autogenerated>:1 +0x5d
main.init()
        <autogenerated>:1 +0x78

처음엔 뭘 잘못 했나 싶어서 이것 저것 해봤는데, 동일 증상…

그러다 이런 글 발견
Driver is being registered twice · Issue #238 · lib/pq · GitHub

이번 문제는 vendor에 있는 파일(lib/pg)을 한번 호출 하고, 다시 GOPATH 에 있는 파일을 호출한 것.

수정을 위해 dep ensure 실행을 해줍니다.
그리고 다시 컴파일 후 실행하면 정상동작합니다.

Kong api gateway 설정 – 2

Kong api gateway 설정 – 2

여기서는 플러그인 활성화 하고 적용 및 테스트 하는 과정을 기록합니다.

첫번째 내용은 여기에..
Kong api gateway 설정 – 1 | 9to5의 개발하면서 겪은 경험

인증 auth


key auth

이제 인증 플러그인을 활성화 시켜보겠습니다.

Plugins – Key Authentication | Kong – Open-Source API Management and Microservice Management

Enable auth plugin on the service

curl -X POST http://localhost:8001/services/mocha/plugins \
  --data "name=key-auth"

Enable auth plugin on the routes

Routes ID 가 필요합니다.
ID 확인이 필요하면,

curl http://localhost:8001/routes

플러그인 활성화

curl -X POST http://localhost:8001/routes/cbaf40b1-196f-4e2b-9b47-b39d84c681a4/plugins \
    --data "name=key-auth" 

Create a Consumer

curl -X POST http://localhost:8001/consumers/ \
  --data "username=admin"

Create a Key

curl -X POST http://localhost:8001/consumers/admin/key-auth

Output:

{"id":"7026e178-9ab7-4bcc-a76a-2ab6b023b84d","created_at":1531989664000,"key":"fqS0sTQ9jM9qjlHNapnzFTJWfI5KGo8P","consumer_id":"69f5420a-6712-4ca2-8bc9-dcc964ebb4e4"}

이제 키 생성이 완료됐습니다.
위에서 등록한 api server 를 다시 호출 해봅시다.

curl -i -X GET \
  --url http://localhost:8000/ \
  --header 'Host: mocha1'

Output:

HTTP/1.1 401 Unauthorized
Date: Thu, 19 Jul 2018 08:43:33 GMT
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
WWW-Authenticate: Key realm="kong"
Server: kong/0.13.1

{"message":"No API key found in request"}

이제 plugin 이 enable 되어서 키 없이는 api 가 호출 되지 않습니다.

키를 넣고 다시 호출 합니다.

curl -i -X GET \
  --url http://localhost:8000/ \
  --header 'apikey: fqS0sTQ9jM9qjlHNapnzFTJWfI5KGo8P' \
  --header 'Host: mocha1'

Response code 200 확인 했습니다.

ldap

Plugins – LDAP Authentication | Kong – Open-Source API Management and Microservice Management

curl -X POST http://localhost:8001/routes/cbaf40b1-196f-4e2b-9b47-b39d84c681a4/plugins \
    --data "name=ldap-auth"  \
    --data "config.hide_credentials=true" \
    --data "config.ldap_host=ldap.example.com" \
    --data "config.ldap_port=389" \
    --data "config.start_tls=false" \
    --data "config.base_dn=dc=example,dc=com" \
    --data "config.verify_ldap_host=false" \
    --data "config.attribute=cn" \
    --data "config.cache_ttl=60" \
    --data "config.header_type=ldap

Allow multiple authentication methods per API and/or Consumer · Issue #590 · Kong/kong · GitHub

Kong의 ldap plugin 은 현재까지는 bind_dn을 입력할 수 가 없습니다.

아래 처럼 PR이 들어가 있는거 같긴 한데… 진행되고 있진 않는 듯합니다.
ldap_auth add bind+search feature by daurnimator · Pull Request #3093 · Kong/kong · GitHub

회사 ldap은 거의 bind_dn 을 사용할 것으로 보기 때문에 과감히 버려줍시다…

로그 logging

syslog 나 tcp-log 는 docker 상태에서는 동작하지 않는 것으로 보입니다.

해서, file-log plugin 을 enable해서 사용하는게 가장 간단한 것으로 보입니다.

file-log

file-log를 사용하기 위해서는 nginx 실행시 user 를 root 로 하는 것이 가장 간단 합니다. 아니면 아래의 에러를 보기 쉽상…..

[error] 76#0: [lua] handler.lua:56: [file-log] failed to open the file: Permission denied, context: ngx.timer, client: xxx.xxx.xxx.xxx, server: 0.0.0.0:8000

첫 번째 글에서 kong 을 docker 로 실행할 때 몇 개의 environment variables 이 추가 되어야합니다.

docker run -d --name kong \
    --link kong-database:kong-database \
    -e "KONG_DATABASE=postgres" \
    -e "KONG_PG_HOST=kong-database" \
    -e "KONG_PROXY_ACCESS_LOG=/dev/stdout" \
    -e "KONG_ADMIN_ACCESS_LOG=/dev/stdout" \
    -e "KONG_PROXY_ERROR_LOG=/dev/stderr" \
    -e "KONG_ADMIN_ERROR_LOG=/dev/stderr" \
    -e "KONG_ADMIN_LISTEN=0.0.0.0:8001" \
    -e "KONG_ADMIN_LISTEN_SSL=0.0.0.0:8444" \
    -e "KONG_LOG_LEVEL=info" \
    -e "KONG_NGINX_USER=root" \
    -p 8000:8000 \
    -p 8443:8443 \
    -p 8001:8001 \
    -p 8444:8444 \
    kong:0.14.0-alpine

위 명령대로 다시 kong 을 실행하면 모든 로그가 docker logs 로 리다이렉트 되고, file-log를 활성화 시킬 수 있는 요건을 갖추게 됩니다.

plugin 활성화

  • services
curl -X POST http://localhost:8001/services/c5414169-73f8-4220-881a-0fb09a12973a/plugins \
    --data "name=file-log"  \
    --data "config.path=/dev/stdout"
  • plugins
curl -X POST http://localhost:8001/routes/a57bf14c-4c77-4990-8709-fb94ce006d2b/plugins \
    --data "name=file-log"  \
    --data "config.path=/dev/stdout"

로그 확인

docker logs -f --tail 100 kong

위 명령어로 도커 로그를 확인하면 이제 정상적으로 api 호출시에 로그가 찍히는 것을 확인 할 수 있습니다.

Kong api gateway 설정 – 1

Kong api gateway 설정 – 1


RESTful api 를 외부로 노출하지 않고 사용하기 위해(authentication, audit 기능 사용을 위한) Kong API 도입을 검토중입니다.

그러기 위해 직접 테스트 해보면서 내용을 여기에 정리하려 합니다.
관련된 모든 설치는 Docker 를 사용 하는 것으로 진행합니다.

Prerequisite

  • kong 0.13.1
  • dashboard v3.3.0
  • postgres 9.6

설치


kong 을 사용하기 위해 database 설정이 필요합니다.

CassandraPostgresql 중 선택 가능하지만 여기서는 postgresql 로 진행합니다.

postgresql

execute

sudo docker run -d --name kong-database \
              -p 5432:5432 \
              -e "POSTGRES_USER=kong" \
              -e "POSTGRES_DB=kong" \
              postgres:9.6-alpine

db migration

sudo docker run --rm \
    --link kong-database:kong-database \
    -e "KONG_DATABASE=postgres" \
    -e "KONG_PG_HOST=kong-database" \
    kong:latest kong migrations up

Kong

sudo docker run -d --name kong \
    --link kong-database:kong-database \
    -e "KONG_DATABASE=postgres" \
    -e "KONG_PG_HOST=kong-database" \
    -e "KONG_PROXY_ACCESS_LOG=/var/log/stdout" \
    -e "KONG_ADMIN_ACCESS_LOG=/var/log/stdout" \
    -e "KONG_PROXY_ERROR_LOG=/var/log/stderr" \
    -e "KONG_ADMIN_ERROR_LOG=/var/log/stderr" \
    -e "KONG_ADMIN_LISTEN=0.0.0.0:8001" \
    -e "KONG_ADMIN_LISTEN_SSL=0.0.0.0:8444" \
    -p 8000:8000 \
    -p 8443:8443 \
    -p 8001:8001 \
    -p 8444:8444 \
    -v /root/log:/var/log \
    kong:0.13.1-alpine

Kong dashboard

Dashboard 는 사실 여기 글에서는 사용하지 않습니다.
Dashboard를 사용하기 위해서는 Dashboard 에 접속 후 UI 상에서 Creaate api를 해줘야합니다. (services 라는 메뉴가 없기 때문, apis 를 직접 등록하는 듯합니다.)
여기서는 따로 dashboard 관련 내용을 포스팅 하진 않겠습니다.

sudo docker run -d -p 8080:8080 \
  --link kong:kong \
  --name kong-dashboard \
  pgbi/kong-dashboard start \
  --kong-url http://kong:8001 \
  --basic-auth admin=admin

이제 설정을 완료했습니다.

실행

이제 실행을 위해 서비스를 등록 하겠습니다.

Configuring a Service – v0.13.x | Kong – Open-Source API Management and Microservice Management

이 링크를 참고했습니다.

  1. 서비스 생성

실제 접속해야할 endpoint (api server address)에 대한 url 을 입력해줍니다.

curl -i -X POST \
  --url http://localhost:8001/services/ \
  --data 'name=mocha' \
  --data 'url=http://10.110.10.124:8080' 
  1. 서비스의 호스트 생성

여기서는 hostname 을 지정해줍니다.
지정해준 이름을 사용해서, 실제. API 를 호출할 때 Host헤더를 이용해서 어느 서비스로 routing 할지를 판단합니다.

curl -i -X POST \
  --url "http://localhost:8001/services/mocha/routes/" \
  --data 'hosts[]=mocha1'
  1. 서비스 정상 작동 여부 확인

위에서 등록한 mocha1이라는 hostname 을 이용해서 실제 api server endpoint(여기서는 http://10.110.10.124:8080) 으로 routing 합니다.

curl -i -X GET \
  --url http://localhost:8000/ \
  --header 'Host: mocha1'

정상 작동 했다면 api server 의 response 를 받을 수 있습니다.

이제 설치는 완료했고, 다음은 인증을 위한 plugin 설치 입니다.

피타고라스 계산기

배관일하는 친구가 자꾸 삼각형의 빗변(피타고라스….)를 구해달라고 한다. 계산기가 없다면서…

귀찮아서 여기에 만들어두고 링크를 주련다…

 

밑변
높이

빗변