Skip to main content

10 posts tagged with "k8s"

View All Tags

쿠버네티스 환경에서의 Node.js DNS Cache

· 5 min read

DNS Cache

쿠버네티스 환경에서는 Node.js 이미지를 올릴 시 종종 아래와 같은 IP 반환 에러메세지가 보인다. 이는 서비스 연결조차 불가능하게 만들어 운영에 지장을 주었다.

Error: getaddrinfo EAI_AGAIN your-service

먼저 Node.js 레벨에서부터 확인을 시작하였다.

UV_THREAD_POOL_SIZE

Node.js Man 을 보면 다음과 같은 주의사항이 있다.

Though the call to dns.lookup() will be asynchronous from JavaScript's perspective, it is implemented as a synchronous call to getaddrinfo(3) that runs on libuv's threadpool. This can have surprising negative performance implications for some applications, see the UV_THREADPOOL_SIZE documentation for more information.

연관된 UV_HTREADPOOL_SIZE 문서는 다음과 같다.

Asynchronous system APIs are used by Node.js whenever possible, but where they do not exist, libuv's threadpool is used to create asynchronous node APIs based on synchronous system APIs.

스레드 풀을 사용하는 API 는 다음과 같다.

  • all fs APIs, other than the file watcher APIs and those that are explicitly synchronous
  • asynchronous crypto APIs such as crypto.pbkdf2(), crypto.scrypt(), crypto.randomBytes(), crypto.randomFill(), crypto.generateKeyPair()
  • dns.lookup()
  • all zlib APIs, other than those that are explicitly synchronous

이 메소드들을 사용할 때는 병렬 요청과 부하에 신경을 써야하며 UV_THREADPOOL_SIZE 사이즈를 Node.js 기본값인 4에서 적절히 증가시켜줘야한다.

dns.lookup

dns.lookup가 비동기인척하는 동기 메소드임을 확인할 수 있었다. 보통 이 메소드를 직접 사용하는 경우는 거의 없다.

axios 등 http 연결을 하는 라이브러리에서 Hostname 을 IP 로 변경하기 위해 사용한다.

java 는 30s¹, php 는 120s²의 도메인 캐시를 기본으로 제공하지만 Node.js 에서는 그런 것이 없다.

Node.js 커뮤니티에서는 native dns lookup cache 기능이 제안 되었지만, dns.resolve4dns.resolve6 에 서버에서 반환하는 ttl 값을 사용할 수 있게 추가되어 이걸 사용하여 DNS 캐싱을 하게 권장되었다.

alpine

alpine 이미지에는 musl 을 사용하므로 다음과 같은 이슈가 발생할 수 있으나 해당 서비스는 그렇지 않았다.

kube-dns

남은 건 쿠버네티스 환경이었다. kube-dns 대신 dnsmasq 를 사용하자 제안이 있었고, DNS intermittent delays of 5s 이슈와 공식 문서의 NodeLocal DNS Cache Daemonset 으로 이 문제를 해결할 수 있어보였다.

클러스터에서 데몬셋을 통해 캐싱된 DNS 를 리졸브하는 완전한 해결책이였으나 클라우드에는 접근 권한이 없어 이슈 내용을 공유할 수 밖에 없었다.

그렇다면 어플리케이션에서 해결할 방법을 찾아야했다.

cacheable-lookup

Node.js 의 HTTP 모듈에서는 lookup 속성을 지원하며 이는 기본값으로 dns.lookup 을 사용한다. 결국 Node.js(HTTP -> dns.lookup) -> Alpine(getaddrinfo) -> K8S(socket) 의 어느 구간이라도 캐싱을 하면 되는 것이다.

dns.lookup 대신에 dns.resolve4 를 사용하며 Map 기반으로 캐시키를 관리하는 라이브러리로 Cachable Lookup 을 찾을 수 있었다.

axios 라이브러리는 lookup 속성을 지원하지 않으므로 http.globalAgent 에 다음과 같이 추가해야했다.

import CacheableLookup from "cacheable-lookup";

const cacheable = new CacheableLookup();
cacheable.install(http.globalAgent);

전역 agent 를 오염시키는 느낌이라 아예 HTTP 라이브러리를 got 으로 변경하였다. got 에서는 dnsCache: true 를 주어 이 기능을 쉽게 활성화 할 수 있었다.

결론

  • 언어 수준에서의 DNS 캐시는 짧게나마 필요해보였다.
  • deno 에선 reqwest 모듈 위에 HTTP 를 올려놓았는데, trust-dns 란 DNS 캐시 모듈을 활성화하는 옵션은 들어가있지 않았다. 따라서 쿠버네티스 환경에서 같은 오류를 뱉을지 테스트해보고 싶다.

참조

쿠버네티스 리눅스 커널 튜닝하기

· 3 min read

변경가능한 커널 설정

노드 레벨의 sysctl과 네임스페이스 sysctl과 같은 커널 파라미터를 sysctl 인터페이스로 변경할 수 있다. 변경 가능한 파라미터는 다음과 같다.

  • abi: 실행 도메인 특성
  • fs: 특정 파일 시스템, 파일 핸들링, inode, dentry, 쿼터 조정
  • kernel: 전역 커널 설정 변경
  • net: 네트워킹
  • sunrpc: SUN rpc 호출
  • vm: 메모리 조정, 버퍼 및 캐시 관리
  • user: 사용자별 네임스페이스 제한

taint, toleration 을 같이 사용해 사이드이펙을 방지하라고 권하고 있다.

ARP 캐시

neighbour: arp_cache: neighbor table overflow!

쿠버네티스가 대량의 IP를 소비하면서 ARP 캐시 공간을 모두 사용할 경우 ARP 캐시 관련 변수 조절이 가능하다. 대규모 HPC 클러스터에서는 흔한 일이며 쿠버네티스의 주소 소진을 방지할 수 있다. 이 오류는 nodes with 40+ cores && more than 16 segments in each node 정도에서 발생하는 듯 하다.

  • net.ipv4.neigh.default.gc_thresh1: gc_thresh1 represents the minimum number of entries that may be in the ARP cache. Garbage collection will not be triggered if the number of entries is below this setting.
  • net.ipv4.neigh.default.gc_thresh2: gc_thresh2 represents the soft maximum number of entries that may be in the ARP cache. This setting is arguably the most important, as ARP garbage collection will be triggered ~5s after reaching this soft maximum.
  • net.ipv4.neigh.default.gc_thresh3: gc_thresh3 represents the hard maximum number of entries in the ARP cache.
net.ipv4.neigh.default.gc_thresh1 = 80000
net.ipv4.neigh.default.gc_thresh2 = 90000
net.ipv4.neigh.default.gc_thresh3 = 100000

예시

apiVersion: v1
kind: Pod
metadata:
name: sysctl-example
spec:
securityContext:
sysctls:
- name: kernel.shm_rmid_forced
value: "0"
- name: net.core.somaxconn
value: "10000"
- name: kernel.msgmax
value: "65536"
- name: fs.file-max
value: "2097152"
- name: net.ipv4.ip_local_port_range
value: "1024 65536"

여담

  • 파면 팔수록 리눅스부터 다시 정리해야되겠다는 느낌이 든다.

참조

Dockerfile의 모든 것

· 7 min read

인스트럭션

  • FROM: 빌드하는 이미지의 기반 이미지 지정
  • RUN: 이미지 빌드 시 컨테이너에서 실행할 명령어 정의
  • COPY: 호스트에서 컨테이너로 파일 및 디렉토리 복사
  • ADD: COPY + 압축 해제 + URL 다운로드
    • 운영 체제를 담은 기반 이미지를 만드는 경우처럼 특수한 경우에만 사용하면 된다.
    • 안정성 보장이 되지 않으므로 COPY 를 사용하자.
  • CMD: 컨테이너에서 foreground로 실행할 명령어 정의
  • ENTRYPOINT: 컨테이너를 실행 가능 파일로 사용할 때 정의하는 명령
    • CMD 와 ENTRYPOINT 둘 다 사용 가능
  • ARG: docker image build를 실행할 때 사용하는 변수
  • ENV: 컨테이너 안의 환경변수 정의
  • EXPOSE: 컨테이너가 노출하는 포트
  • VOLUME: 호스트나 다른 컨테이너에서 마운트할 수 있는 포인트 생성
  • LABEL: 이미지에 추가하는 메타데이터
  • STOPSIGNAL: 컨테이너에 전달되면 컨테이너를 종료하는 시스템 시그널 설정
  • HEALTHCHECK: 컨테이너 안에서 명령을 실행 후 결과를 헬스 체크에 사용
  • USER: 컨테이너 실행 시 컨테이너 사용자
    • 이미지 빌드시 USER 뒤에 나오는 RUN 인스트럭션도 해당 사용자의 권한으로 실행된다.
  • WORKDIR: 컨테이너의 작업 디렉토리
  • ONBUILD: 컨테이너 안에서 실행되는 명령 정의, 이미지에서 실행되지 않는다.
    • ONBUILD 를 정의한 이미지를 기반 이미지로 삼아 다른 이미지를 빌드할 때 실행된다.

이미지

린팅

hadolint 를 설치해 Dockerfile best practices 에 기반해 이미지를 생성했는지 검증하자.

기반 이미지

  • scratch: 아무 것도 없는 이미지
    • https 통신이 필요한 경우 cacert.pem 을 /etc/ssl/certs 에 추가해야한다.
    • 디버깅도 힘들다
  • busybox: 기본 유틸리티 (echo, ls 등) 이 있는 이미지
    • 패키지 관리자가 없다.
    • 디버깅은 좀 낫다.
  • alpine: busybox 기반으로 4MB 지만 apk 패키지 매니저가 있다.
    • glibc 대신 musl을 쓴다.
    • apk add --no-cache package
    • apk add --no-cache --virtual=ailas package && apk del --no-cache ailas

멀티스테이지 빌드

golang과 같은 빌드가 필요한 이미지에서는 멀티스테이지 빌드를 이용해 빌드 환경과 프로덕션 환경을 다르게 가져갈 수 있다.

FROM golang:1.9 AS build

WORKDIR /
COPY . /go/src/github.com/...
RUN go get gokpg.in/gorp.v1
RUN cd /go/src/github.com/... && go build -o bin/start main.go

FROM alpine:3.7
COPY --from=build /go/src/.../bin/start /usr/local/bin/
CMD ["start"]

distroless 이미지

  • 운영체제 기능은 없이 언어에 중점을 둔 이미지이다.
  • distroless 에서 확인 가능하며 주로 구글이 배포한다.
  • gcr.io/distroless/base 이미지는 glibc 기반이며 컴파일 애플리케이션을 실행하는 데에 적합하다. (Go)
  • ca-certificates 및 TLS/SSL 관련 라이브러리 등 최소한의 라이브러리만 있다.
  • CVE 취약점도 업데이트 된다고 한다.
FROM node:10.17.0 AS build-env
ADD . /app
WORKDIR /app

FROM gcr.io/distroless/nodejs
COPY --from=build-env /app /app
WORKDIR /app
CMD ["hello.js"]

chucksum 검증

ADD 인스트럭션으로 추가 된 파일은 해시기반 체크섬 검증을 해주는 것이 좋다.

ADD library.zip .
ADD library_SHA256 .
ADD library_SHA256.sig .

## Import PGP public key
RUN curl https://.../pgp_keys.asc | gpg --import

## 라이브러리 전자 서명 검증
RUN gpg --verify library_SHA256.sig library_SHA256

## Verify checksum
RUN cat library_SHA256 | grep linux_amd64 | sha256sum -cs
RUN unzip libary.zip
RUN mv library /usr/local/bin
## 실행

dockerigonore

  • Dockerfile 빌드 시에 따라 들어가지 않게 된다.
  • Dockerfile 과 같은 레벨 디렉토리에 있어야한다.
.dockerignore
.git
.idea
.vscode
.github
*.log

이미지 테스트

빌드 후의 이미지 내부에 상태가 적절한지 테스트하기 위해 아래 두 가지 yaml 기반의 테스트 툴을 사용할 수 있다.

이 중 goss는 실제 포트 및 서비스가 서빙 중인지 확인이 가능해 더 유용할 것으로 보인다.

이미지 보안

user

호스트의 리소스를 컨테이너에서 공유하는 Docker는 사용자 UID도 0으로 같이 공유되므로 같은 권한을 갖게 된다. 이 문제를 방지하기 위해 useradd 로 어플리케이션 실행 유저를 만들어 주고 USER 인스트럭션을 사용해 실행을 해줘야한다.

FROM golang:1.10

RUN mkdir /app
COPY main.go /app

RUN useradd gracefullight
USER gracefullight

CMD ["go", "RUN", "/app/main.go"]

secret

dockerd 튜닝

  • max-concurrent-downloads: 기본값은 3이며, docker image pull 로 한 번에 다운로드 되는 이미지 스레드 수를 증가시켜준다.
  • max-concurrent-uploads: 기본값은 5이며, docker image push 시에 이미지 업로드 스레드 수를 증가시켜준다.
  • registry-mirrors: Docker hub의 미러 레지스트리를 만들어 트래픽 향상에 이점을 줄 수 있다.

private registry

빠른 이미지 푸쉬/풀과 소스 때문이라도 private registry 는 필수적이다. docker 에서 제공하는 registry 이미지를 사용하면 된다.

GUI 기반으로 확인할 수 있는 툴은 아래와 같다.

  • Harbor: 프라이빗 레포지토리를 위한 모든 기능이 다 있다. 쓰자.
  • Portus: 인증 포함, 하지만 루비라 소스 개선이 힘들듯
  • docker-registry-ui: 20년 최근까지 개선 중

쿠버네티스 로그 아키텍쳐

· One min read

관리 원칙

  • 어플리케이션 로그는 모두 stdout 으로 출력해야한다.
    • 컨테이너로 운영하는 것을 전제로 한다면 파일 출력 자체가 불필요하다.
  • Nginx 등의 미들웨어에서는 로그가 stdout 으로 출력되도록 이미지를 빌드한다.
  • stdout 으로 출력되는 로그는 모두 JSON 포맷으로 출력해 각 속성을 검색할 수 있게 한다.
  • 쿠버네티스 환경에서는 fluentd-kubernetes-daemonset 을 포함하는 파드를 DaemonSet을 사용해 각 호스트에 배치한다.
  • 쿠버네티스 리소스에서는 적절히 레이블을 부여해 로그를 검색할 수 있게 한다.

쿠버네티스 롤링 업데이트와 배포

· 5 min read

Deployment

Deployment 의 파드 교체 전략에는 RollingUpdateRecreate 가 있다. 기본 값은 RollingUpdate 이며 간단한 설정이 적용된 디플로이먼트는 아래와 같을 것이다.

apiVersion: apps/v1
kind: Deployment
metadata:
name: test-rolling-update
label:
app: test
spec:
replicas: 4
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 3
maxSerge: 4
selector:
matchLabels:
app: test
template:
metadata:
labels:
app: test
spec:
containers:
- name: test
image: echo
ports:
- containerPort: 8080

여기서 롤링업데이트 속성을 좀 더 자세히 알아보자.

RollingUpdate

maxUnavailable

  • 롤링 업데이트 중 동시에 삭제할 수 있는 파드의 최대 갯수
  • 기본 값은 replicas의 25% 이다.
  • replicas: 4의 경우 1개 파드 삭제, replicas: 8의 경우 2개 파드 동시 삭제
  • 퍼센트 및 직접 지정이 가능하다.
  • 위의 예시 디플로이먼트에서 롤링업데이트 시작 시 파드 3개가 바로 죽는다.
  • 이 값을 높게 설정하면 동시에 교체되는 파드가 늘어나므로 롤링 업데이트 시간이 줄어든다.
    • 하지만 롤링업데이트 중에 남아 있는 파드에 요청 수가 몰릴 수 있다.
    • 따라서 1로 설정해 파드를 하나씩 교체하는 것이 안전할 수 있다.

maxSurge

  • 롤링 업데이트 중 동시에 생성하는 파드 갯수
  • 기본 값은 replicas의 25% 이다.
  • replicas: 4면서 maxSurge: 4면 롤링 업데이트 시작 시 새 버전의 파드가 4개 추가로 생성된다.
  • 이 값을 높게 설정하면 필요한 파드를 빨리 생성하므로 파드 교체 시간이 단축된다.
    • 하지만 필요한 시스템 자원이 급증할 수 있다.

Probe

세부 설정은 Docs를 참조하자

livenessProbe

  • 애플리케이션 헬스 체크 기능
  • 애플리케이션이 의존하는 컨테이너 안의 파일의 존재여부 확인
  • Unhealthy 상태의 경우 파드 재시작

readinessProbe

  • 컨테이너 외부에서 HTTP 등의 트래픽을 발생시켜 처리할 수 있는 상태인지 확인
  • tcpSocket으로 포트 지정도 가능하다.

응답 중인 파드 교체

응답 중인 파드가 교체되는 경우 SIGTERM 신호를 보내 파드가 삭제되는데, Graceful Shutdown 상태로 만들기 위해서 종료 처리가 오래 걸리는 파드엔 terminationGracePeriodSeconds 를 설정해주는 것이 좋다.

spec:
# 기본값 30
terminationGracePeriodSeconds: 40
containers:
- name: maria
image: maria:latest

Nginx처럼 SIGTERM 시그널을 받고 바로 종료되는 어플리케이션이 있는 파드라면, 라이플사이클 훅을 활용해 안전하게 종료시키는 것이 중요하다.

spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
lifecycle:
# 파드 종료 전 훅
preStop:
exec:
command: ["/usr/sbin/nginx", "-s", "quit"]

파드 1:1 교체

그렇다면 replicas: 1인 파드를 1:1 교체 시에는 어떤 전략을 가져가야할까 답은 아래와 같다.

strategy:
type: RollingUpdate
rollingUpdate:
# 롤링 업데이트 시 삭제 되는 파드 수
maxUnavailable: 0
# 롤링 업데이트 시 새로 생성되는 파드 수
maxSurge: 1

여기에 파드가 트래픽을 받을 수 있는지 readinessProbe를 추가해주면 된다.

readinessProbe:
httpGet:
path: /ping
port: 80
initialDelaySeconds: 5
periodSeconds: 5
successThreshold: 1
failureThreshold: 3
timeoutSeconds: 3

5초 후에 5초마다 /ping 을 보내 성공여부를 확인한다. 타임아웃은 3초며 3번까지 재시도한다.

Blue/Green

  • 디플로이먼트를 2개 만들고, 서비스의 selector 값을 라벨에 따라 변경하자.
  • 서비스메시를 연동하면 카나리아 배포 방식도 가능하다.

Helm chart의 모든 것

· 5 min read

Helm

Helm is a tool for manaing Kubernetes charts. Charts are packages of pre-configured Kubernetes resources.

  • 헬름은 쿠버네티스 차트를 관리하기 위한 도구
  • 차트는 사전 구성된 쿠버네티스 리소스의 패키지
  • 같은 어플레케이션을 여러 환경에 배포시 환경 변수, 도메인 등의 manifest 파일을 차트를 통해 관리
  • 차트를 중심으로 하는 쿠버네티스 개발 종합 관리 도구

설치

## 설치
brew install kubernetes-helm

## 초기화
helm init

## tiller 파드 확인
kubectl -n kube-system get service,deployment,pod --selector app=helm

## 버전 확인
helm version

구성

  • cli와 쿠버네티스 클러스터에 설치되는 서버인 tiller(틸러)로 구성

chart

  • 쿠버네티스는 service, deployment, ingress 등 리소스를 생성하고 manifest 파일을 적용하는 방식으로 어플리케이션을 배포한다. 이 manifest 파일을 생성하는 템플릿을 여러 개 패키징한 것
  • helm repository 에 tgz 파일로 저장

chart 구성

chart-example
├── charts # 차트가 의존하는 차트 디렉토리
├── templates # manifest 파일 템플릿 디렉토리
│ ├── NOTES.txt # 차트 사용법 등 참조 문서 템플릿
│ ├── _helper.tpl # manifest 렌더링에 사용되는 템플릿 헬퍼
│ └── example.yaml # 각종 K8S 리소스의 manifest 템플릿
├── Chart.yaml # 차트 정보가 정의 파일
└── values.yaml # 차트 기본값 value 파일

차트 설치시 values.yaml 에 override 할 값을 정의한 yaml 파일을 만들면 된다.

repository

  • local: 헬름 클라이언트가 설치된 로컬 리포, 로컬에서 생성한 패키지가 존재
  • stable: stable charts repo, 기본값이며 helm/charts 차트 사용 가능
  • incubator: stable 조건 미달 repo
    • helm repo add incubator https://kubernetes-charts-incubator.storage.googleapis.com/
  • helm search 로 차트 검색 가능

명령어

init

보통은 helm init 실서버에서는 helm init --service-account tiller --node-selectors system --history-max 10 업그레이드는 helm init --upgrade

  • --service-account: 틸러가 사용할 서비스계정
  • --node-selectors: 틸러를 배포할 노드 레이블
  • --upgrade: 틸러 업그레이드
  • --history-max: 리소스 하나당 유지할 최대 히스토리 수

create

helm create 차트명

package

helm package 차트명 차트를 압축파일로 패키징

  • --version: 차트 버전 지정
    • 없을 경우 차트의 버전을 따른다.

helm search 검색어

  • -r, --regexp: 검색어를 정규표현식으로 사용
  • -l, --versions: 버전 목록도 출력

fetch

helm fetch 차트경로

  • --version: 특정 버전 지정

serve

helm serve 로컬 레포지토리로 사용할 웹 서버 시작

  • --address: 서버가 개방할 주소
    • 기본값은 127.0.0.1:8879
  • --repo-path: 차트 레포지토리가 될 로컬 디렉토리

install

helm install 차트명 차트로 애플리케이션 설치

helm install stable/redis --name my-redis

helm install stable/redis --version 3.6.0 \
--name my-redis \
--namespace gracefullight \
-f ./my-redis.yaml
  • --dry-run
  • --name: 릴리즈명
  • --namespace: 설치 대상 네임스페이스
  • -f, --values: yaml 파일 경로 (다수 가능)
  • --version: 차트 버전 지정

list

helm list helm list --namespace kube-system

  • --deleted: 삭제된 릴리즈 포함
  • --namespace: 해당 네임스페이스만 확인

get

helm get 릴리즈명 설치된 릴리스 상ㅇ세 정보를 yaml 으로 출력

  • --revision: 릴리즈 리비전 확인

delete

helm delete 릴리즈명

  • --purge: 릴리즈 삭제하고 릴리즈명 해제

쿠버네티스 오브젝트와 클래스 오브젝트 비교

· 2 min read

앞서

쿠버네티스를 이해하는 건 어렵다. DevOps 의 전반적인 플로우를 알아야 이 플로우도 이해가 가기 때문이라 생각한다. 생소한 용어들도 어렵다. 한 판을 정리 했지만 매번 참조해야되는 듯하다.

오브젝트 비교

개념JAVA 클래스쿠버네티스 오브젝트
캡슐화ClassContainer image
인스턴스ObjectContainer
재사용단위jarContainer image
컴포지션Class A Contains Class BSidecar pattern
상속Class A extends Class BA container's from parent image
배포단위jar, war...Pod
빌드 및 런타임 아이솔레이션Module, Package, ClassNamespace, Pod, Container
초기화ConsturctorInit container
초기화 후 트리거Init methodpostStart
종료 전 트리거DestroypreStop
Cleanupfinalize(), shutdown hookDefer container
비동기, 병렬 실행ThreadPoolExecutor, ForkJoinPoolJob
스케쥴링Timer, ScheduledExecutorServiceCronJob
백그라운드Daemon threadDaemonSet
설정관리System.getenv(), PropertiesConfigMap, Secret

여담

  • 개념 설정 전에 이 표를 알았더라면 훨씬 이해가 쉬웠을텐데

Kubernetes의 모든 것

· 9 min read

매번 용어 찾아보는 게 귀찮아서 정리했다. 어떤 기능을 yaml 로 구성해야하는지 감이 오는 듯하다.

노드

  • 컨테이너가 배치되는 서버
  • 쿠버네티스 클러스터의 관리 대상으로 등록된 도커 호스트
  • 쿠버네티스 클러스터는 마스터와 노드의 그룹으로 구성
  • kubectl get nodes

마스터 노드

  • 스웜의 매니저노드와 비슷한 느낌이다.
  • kube-apiserver: 쿠버네티스 API를 노출하는 컴포넌트 kubectl로부터 리소스를 조작하라는 지시를 받음
  • ectd: 고가용성을 갖춘 분산 Key/Value 스토어로 쿠버네티스 클러스터의 백킹스토어로 쓰인다. (CAS)
  • kube-scheduler: 노드를 모니터링하고 컨테이너를 배치할 적절한 노드탐색
  • kube-controller-manager: 리소스를 제어하는 컨트롤러 실행

일반적인 (non-managed, GKE 등이 없는) 환경에선 마스터 노드서버가 단일 장애지점 (SPOF)가 되지 않도록 마스터를 3대 두는 것이 필수다.

네임스페이스

  • 쿠버네티스 클러스터 안의 가상 클러스터
  • 네임스페이스마다 권한을 줄 수 있으므로 개발팀이 클 때 유용하다.
  • 기본으론 default, docker, kube-public, kube-system 네임스페이스가 있다.
  • kubectl get namespace

파드

  • 컨테이너의 집합 중 가장 작은 단위로, 컨테이너의 실행 방법을 정의
  • 적어도 하나 이상의 컨테이너로 이뤄져있다.
    • nginx reverse proxy + application web server
  • 파드는 노드 안에 들어간다.
  • 함꼐 배포해야 정합성, 일관성이 유지된다면, 같은 파드로 묶어 배포한다.
  • 파드엔 각각 고유의 가상 IP가 할당된다.
  • kubectl get pod

레플리카세트

  • 같은 스펙을 갖는 파드를 여러 개 생성/관리 하는 역할
  • 똑같은 파드의 레플리케이션 개수를 관리하는 리소스이다.
  • 삭제된 파드는 복원할 수 없기 때문에 웹 어플리케이션과 같은 stateless 파드에 사용하기에 좋다.
  • kubectl get replicaset
  • 레플리카셋의 파드명은 container-1b2e3f 처럼 임의로 정해진다.

디플로이먼트

  • 레플리카세트의 리비전을 관리 (빠른 버저닝)
  • 리비전은 컨테이너 이미지가 수정된 경우에 생성된다.
  • kubectl rollout history deployment ..

데몬셋

  • 모든 시스템에서 실행되어야하는 파드와 템플릿 제공
  • 노드 당 하나

서비스

  • 파드의 집합에 접근하기 위한 경로를 정의
  • 쿠버네티스 클러스터 내에서 파드의 집합에 대한 경로나 서비스 디스커버리를 제공하는 리소스
  • 서비스의 대상이 되는 파드는 서비스에서 정의하는 레이블 셀렉터로 정해진다.
  • 서비스의 접근은 같은 네임스페이스라면 서비스명만으로 접근이 가능하다.
    • http://${svc} => http://${svc}.default => http://${svc}.default.svc.local

ClusterIP 서비스

  • 서비스의 기본 타입
  • 쿠버네티스 클러스터의 내부 IP 주소에 서비스를 공개할 수 있다.
  • 다른 파드 그룹으로 접근할 때 서비스를 거쳐 가도록 할 수 있으며, 서비스명으로 네임 레졸루션이 가능해진다.
  • 외부에서는 접근할 수 없다.

NodePort 서비스

  • 쿠버네티스 클러스터 외부에서 접근할 수 있는 서비스
  • 서비스를 L4 레벨에서 노출하는 것이므로 TCP/UDP 모두 다룰 수 있다.
  • kubectl get svc ...

LoadBanlancer 서비스

  • 로컬 쿠버네티스 환경에선 사용할 수 없다.

ExternalName 서비스

  • 쿠버네티스 클러스터에서 외부 호스트를 네임 레졸루션하기 위한 별칭이다.
  • gracefullighut.dev 를 gracefullight 로 접근 가능하게 만든다.

인그레스

  • 서비스를 쿠버네티스 클러스터 외부로 노출
  • NodePort 서비스는 L4라 HTTP/S의 경로기반 라우팅이 불가능한데, 인그레스는 L7 레벨로 제어가 가능하다.
  • 주로 HTTP/S 서비스를 노출하는 경우 사용한다.
  • 로컬 쿠버네티스 환경에서는 인그레스를 사용해 서비스를 노출시킬 수 없으므로 nginx ingress controller 를 사용한다.

컨피그맵

  • 설정 정보를 정의 후 파드에 전달

퍼시스턴트볼륨

  • 파드가 사용할 스토리지의 크기 및 종류를 정의

퍼시스턴트볼륨 클레임

  • 퍼시스턴트 볼륨을 동적으로 확보

스토리지클래스

  • 퍼시스턴트 볼륨이 확보하는 스토리지의 종류를 정의

스테이트풀세트

  • 같은 스펙으로 모두 동일한 파드를 여러 개 생성하고 관리
  • 스테이트풀셋의 파드명은 backend-0, backend-1 처럼 인덱스로 정해진다.

  • 일회성 파드를 여러 개 생성하고 종료 보장

시크릿

  • 인증 정보 등 비밀 데이터 정의

  • 네임스페이스 안에서 조작 가능한 쿠버네티스 리소스의 규칙 정의
  • 각 쿠버네티스 API 사용 권한 정의
  • 지정된 네임스페이스 내에서만 유효

롤 바인딩

  • 쿠버네티스 리소스 사용자와 롤을 연결

RBAC

  • Role-based access control (RBAC) is a method of regulating access to computer or network resources based on the roles of individual users within an enterprise.
  • Docs

권한 디버깅

## can-i 문으로 권한 확인 가능
$ kubectl auth can-i ...

## kubectl auth can-i create deployments
## yes

클러스터롤

  • 클러스터 전체적으로 조작 가능한 쿠버네티스 리소스의 규칙 정의
  • 클러스터 전체에서 유효

클러스터롤 바인딩

  • 쿠버네티스 리소스 사용자와 클러스터롤을 연결
  • 일반 사용자 및 그룹/서비스 계정과 클러스터롤을 연결

서비스 계정

  • 파드가 쿠버네티스 리소스를 조작할 때 사용하는 계정
  • 구글의 서비스 계정 비슷한 듯

노드어피니티

  • 노드 셀렉터보다 더 세밀한 선택이 가능 (스케줄링 정책 조건 등)

테인트

  • taint
  • 오염 조건을 추가해 노드 스케줄링에서 제외
  • 파드를 특정 노드로 강제할 수 있다.

컨트롤 플레인

  • kubelet, kubenetes master 등 컨트롤 플레인은 시스템 내 모든 쿠버네티스 오브젝트의 레코드를 유지하면서, 오브젝트의 상태를 관리하는 제어 루프를 지속적으로 구동

Dynamic Admission Control

  • 웹훅으로 동작
  • ValidationWebhookConfiguration
  • MutatingWebhookConfiguration

클러스터 데몬

  • 클러스터 모든 서비스에 검사, 실행 필요시 사용

클러스터어시스턴스

  • SSL 인증 배포 등 자동화
  • cert-manager

Mac에서 Kubernetes is starting이 지속되는 현상

· 2 min read

원인

모하비 문제인지 최근 엣지 버전 Docker for Mac 이 문제인지 잘 모르겠지만, 부팅시에 Kubernates is starting 문구가 지속되며 CPU의 온도를 90도까지 올려버린다.

맥북이 트랜스포머가 되어 곧 제트기가 될 것처럼 굉음이 나는데 해결해보자.

해결

  • Docker > Prefereneces > Reset 에서 Reset to factory defaults 로 공장 초기화를 진행한다.
  • ~/.kube 폴더를 강제로 삭제한다.
  • 다시 쿠버네티스를 실행한다.

여담

쿠버네티스 뿐 아니라 도커 자체가 맥에서 CPU 가 튀는 현상이 있는데, 여기 이슈에서 관리가 되고 있다.

file watch 기능을 끄거나, 볼륨을 해제하거나, 맥 디스크 암호화 기능을 꺼보라는데 신뢰할 수 있는 방법은 아니다.

도커 켜놓고 잠자기 해놓으면 과열로 맥북 켜지지도 않을 수 있어서 해결될 때까진 윈도우에서 돌려야할 듯 싶다.

AWS ECS 부수기

· 6 min read

ECS 는 서울 리젼이 아직 없어서 그런가, 구글링해도 사용할만한 데이터가 너무 적었다. (기초 설명은 잘 되있다. 하지만 Hello World Application 을 올리려고 ECS 를 쓰는 건 아니니까..)

용어

Cluster

Amazon ECS 클러스터는 작업을 배치할 수 있는 컨테이너 인스턴스의 논리적 그룹화입니다. 이 공식 설명을 보고 Cluster 에 대한 감을 잡기가 쉽지 않았다.

  • 간단히하면 Docker Container 를 올리는 EC2 Instance 이다.
  • K8S 의 그 클러스터이다.

Task

  • Task 는 작업이라고 번역되며, 하나의 Task Definition JSON 은 하나의 Docker-compose YAML 이라고 보자.

Container Definition

  • 배포되는 각 컨테이너의 정의
  • 파드의 개념

Task Definition

  • 컨테이너의 집합인 Task의 정의
  • 파드의 개념

Service

Amazon ECS 는 단일 ECS 클러스터에서 작업 정의에 지정된 수("원하는 개수")의 인스턴스를 동시에 실행 및 관리할 수 있게 해줍니다. 어떤 이유로 작업이 실패 또는 중지되는 경우 Amazon ECS 서비스 스케줄러가 작업 정의의 다른 인스턴스를 시작하여 이를 대체하고 서비스의 원하는 작업 수를 유지합니다.

  • Task 를 자동으로 관리할 수 있게 하는 기능, LB 나 Auto Scaling 모두 여기서 적용이 된다.
  • 롤링 업데이트 시에 배포 및 오토 스케일링을 담당한다.
  • K8S의 서비스, 디플로이먼트, 레플리카세트의 역할

Repository

AWS Docker 레지스트리 서비스로 AWS Private DockerHub 라고 보자.

네트워크 모드

모드내용
default네트워크 모드 기본값으로 bridge와 같음
awsvpcAWS에서만 제공되는 네트워크 모드 (ENI가 Task 자체 VPC의 IP 주소할당)
bridgeDockerContainer를 호스트와 같은 네트워크에 배치해 라우팅 없이 컨테이너 접근 가능
hostTask가 배치되는 호스트의 네트워크를 공유하는 모드 (Fargate 사용 불가)
noneTask에 속한 컨테이너의 외부 접근이 불가능하고 포트 매핑 사용불가

볼륨 생성

-v 또는 --volume 으로 Host 의 폴더를 Mount 하는 기능이 꼭 필요한데, 설정 창에선 찾기가 너무 힘들었다. ECS 에서는 작업 정의 생성 시에 하단에 볼륨 추가 를 꼭 먼저 클릭해 볼륨부터 추가해야한다.

image from hexo 이름엔 --name 옵션 사용하듯이 닉네임을 넣고 소스 경로엔 Host directory 경로를 넣자.

추가가 되면 컨테이너 추가 시에 탑재 지점 메뉴의 소스 볼륨 select box 에서 선택할 수 있다.

image from hexo

이 짓을 하는 것보단 공식 문서의 Task Definition JSON Parameter 를 보고 JSON 으로 때려박는게 편하다.

예시 JSON 은 아래와 같다. (3306 과 3307 을 열고 Host 의 Data 폴더를 Mount 하는 기본 구성의 MariaDB Image)

mariadb
{
"requiresAttributes": [
{
"value": null,
"name": "com.amazonaws.ecs.capability.docker-remote-api.1.21",
"targetId": null,
"targetType": null
}
],
"taskDefinitionArn": "your task definition arn",
"networkMode": "bridge",
"status": "ACTIVE",
"revision": 3,
"taskRoleArn": null,
"containerDefinitions": [
{
"volumesFrom": [],
"memory": null,
"extraHosts": null,
"dnsServers": null,
"disableNetworking": null,
"dnsSearchDomains": null,
"portMappings": [
{
"hostPort": 3306,
"containerPort": 3306,
"protocol": "tcp"
},
{
"hostPort": 3307,
"containerPort": 3307,
"protocol": "tcp"
}
],
"hostname": null,
"essential": true,
"entryPoint": null,
"mountPoints": [
{
"containerPath": "/var/lib/mysql",
"sourceVolume": "dbdata",
"readOnly": null
}
],
"name": "maria",
"ulimits": null,
"dockerSecurityOptions": null,
"environment": [
{
"name": "MYSQL_DATABASE",
"value": "db"
},
{
"name": "MYSQL_PASSWORD",
"value": "db_pw"
},
{
"name": "MYSQL_ROOT_PASSWORD",
"value": "root_pw"
},
{
"name": "MYSQL_USER",
"value": "db_user"
}
],
"links": null,
"workingDirectory": null,
"readonlyRootFilesystem": false,
"image": "mariadb:latest",
"command": [
"mysqld",
"--character-set-server=utf8",
"--collation-server=utf8_general_ci"
],
"user": null,
"dockerLabels": null,
"logConfiguration": null,
"cpu": 0,
"privileged": null,
"memoryReservation": 500
}
],
"placementConstraints": [],
"volumes": [
{
"host": {
"sourcePath": "/ecs/dbdata"
},
"name": "dbdata"
}
],
"family": "mariadb"
}

고민

Container 를 Task 별로 생성해야하는데, 그럼 Task JSON 에서 link 옵션을 연결할 수가 없다. 이 경우엔 어떻게 Task Definition 을 짜야되나? EC2 에 접근해서 매번 link 를 생성해서 다시 올려야되나?

이 부분을 해결하기 위해선 ecs-task-kite를 사용하거나 VPC 를 구성해 수동으로 연결해 주는 방법 밖에 없다.

쉬운 방법으로 가자면 DB 는 (모든 컨테이너가 하나의 데이터를 바라봐야하는) RDSElastiCache처럼 AWS 의 서비스 사용하고 VPC 를 구성해 Backend, Frontend 단의 서버만 ECS Task 를 만들어서 가변적으로 돌리는 게 좋아보인다.

물론 동기화를 할 수도 있는데... 삽질할 시간에 더 잘 나온 포스팅을 기다려본다.

여담

그냥 모니터링 컨테이너 하나 더 띄우고, HAProxy 컨테이너 올리고 EC2 에 다 때려박고 싶다.

AWS Korea week in review에 소개되었다.