Skip to main content

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

· 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

여담

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

Github Actions로 Hexo 배포 자동화하기

· 2 min read

개념

Docker 이미지로 생성된 후에 그 위에서 돌아간다. 자세한 개념은 시간날 때 추가 예정

레파지토리 토큰 발급

여기를 참조해 레파지토리의 secret 으로 등록한다.

서브모듈

themes directory 하위의 테마들은 각각의 repo를 가지고 있다. 이 테마들을 CI 중에 가져오기 위해선 서브모듈로 등록해주고 초기화시켜줘야한다.

## 서브모듈 테마 추가
git submodule add 테마깃경로 themes/테마명

## 싱크
git submodule update --init --remote

여기서 remote 옵션을 쓰지 않을 경우 최신 마스터를 pull 하지 않는다. 서브모듈을 쓰는 이유는 내가 관리하지 않기 위함이니 꼭 추가해주자.

소스

주석 없어도 하나하나가 무슨 느낌인지는 받아들여질 것 같다.

name: Node CI

on:
push:
branches:
- master

jobs:
build:
runs-on: ubuntu-latest

strategy:
matrix:
node: [20.x]

steps:
- uses: actions/checkout@master

- name: SETUP_NODE_${{ matrix.node }}
uses: actions/setup-node@master
with:
node-version: ${{ matrix.node }}

- name: BEFORE_INSTALL
run: npm i -g hexo workbox-cli

- name: BEFORE_SCRIPT
run: |
git config --global user.name 'gracefullight'
git config --global user.email '[email protected]'
sed -i "s/__GITHUB_TOKEN__/${{ secrets.HEXO_DEPLOY_TOKEN }}/" _config.yml

- name: THEME_INSTALL
run: |
git submodule update --init --remote --recursive

- name: NPM_INSTALL
run: npm install

- name: HEXO_CLEAN
run: hexo clean

- name: HEXO_GENERATE
run: hexo generate

- name: WORKBOX_BUILD
run: workbox injectManifest

- name: HEXO_DEPLOY
run: hexo deploy

GITHUB_TOKEN

secrets.GITHUB_TOKEN 은 예약된 토큰이다. 빌드 이미지에서 현재 repo를 접근하기 위한 토큰임을 알아두자.

결론

travis-ci 안녕

참조

puppeteer 크롤링 속도 증가시키기

· One min read

페이지를 가져온 뒤 css, image, font를 차단하면 더 빠른 DOM 액세스가 가능하다.

리소스 차단

// @types/puppeteer
import { launch, Browser, Request, Page } from "puppeteer";

const browser: Browser = await launch({
headless: true,
});

const page: Page = await browser.newPage();
await page.setRequestInterception(true);

page.on("request", (req: Request) => {
switch (req.resourceType()) {
case "stylesheet":
case "font":
case "image":
req.abort();
break;
default:
req.continue();
break;
}
});

await page.goto("URL");

Chrome 75 Webview에서 서비스워커의 fetch request 가 실패하는 문제

· 5 min read

발단

  • 서비스워커는 n:m (사이트:디바이스)로 모든 클라이언트 기기에 설치된다.
  • 배포 후 아무 문제 없이 동작하였으나, 190604 이후 웹뷰에서 net::ERR_ABORTED 페이지가 보인다는 버그가 들어오기 시작했다.
  • 랜덤하게 발생되고 있어 추적이 어려웠으나 4일에 Chrome의 메이저 버전이 업데이트 되었다라는 것을 확인했고 디버깅을 시작했다.

디버깅

chromium#973048

  • 서비스워커가 내려오지 않은 경우 페이지가 100% 정상동작을 하였다.
  • 서비스워커가 내려온 경우 랜덤하게 페이지가 로드되지 않았다.
  • 문제는 서비스워커로 확인되었고, 세부적인 디버깅 내역은 다음과 같다.
    • fetchListener 내부 cacheStorage 접근 예외처리: 재현
    • fetchListener 제거: 재현 안됨
  • Chrome 75버전의 웹뷰에서 서비스워커의 fetching 방식이 변경되었다는 걸 확인할 수 있었다.
  • 74, 75버전의 Diff를 찾을 수 있어 서비스워커 코어가 어마어마하게 변경되었다는 걸 확인할 수 있었으나 이 코드를 디버깅하는 것보다 퇴사 후 행복하게 사는 게 멋질 것이란 판단이 들었다.
  • 다행히 구글러와 연락이 닿아 private 버그리포팅을 했고, Chrome은 오픈소스라 구글에서도 일일히 확인하기 힘들다라는 답변을 들을 수 있었다.

웹뷰와 서비스워커

  • 웹뷰의 서비스워커와 브라우저의 서비스워커는 다른 인스턴스이므로 서로 공유되지 않는다.
  • 그렇다면 웹뷰에서 재접속시에 앱 셸을 빠르게 로드하는 이점 뿐이라는 말이다. === 어드벤티지 없음
  • WebView UA in Lollipop and Above에 따르면 안드로이드 롤리팝 이후부터 User Agent에 wv란 값을 물고 들어온다.

해결

예외처리

  • Chrome 엔진이 업데이트 된다고 서비스워커가 제거되지 않는다.
  • 따라서 UA 에 android, wv 값이 있는 경우 서비스워커를 설치하지 않을 뿐 아니라 설치된 서비스워커를 제거해주는 로직이 있어야한다.
  • 아니라면 A way to immediately unregister a service worker 기능을 브라우저 벤더들이 개발해줘야한다.
  • 더 특정한 버전을 줘서 예외처리를 한다면 다음과 같을 것이다.

Chrome 75.0.3770.67 ~ 75.0.3770.101 버전의 모든 안드로이드 웹뷰에서 서비스워커 설치를 차단, 이미 설치가 되어있다면 삭제

패치

  • 몇일 뒤에 다른 업체에서 public 하게 버그리포팅을 올렸고 버그를 찾아서 조만간 패치될 예정이다.
  • This is affecting tens of thousands of our readers 로 보아 나와 같은 빡침이 느껴져서 아련했다.
  • 패치된 코드는 여기서 볼 수 있다.

여담

  • 브라우저에 버그 발생시 대처하는 방법은 거의 불가능하다.
  • 실수는 여기든 저기든 다 똑같구나

맥 구글 계정 연동시 인증 실패

· One min read

맥에서 구글 계정 연동 중에 Google 계정 인증에 실패했다는 오류메세지가 나온다면 다음과 같이 해결하면 된다.

해결

설정 > 일반 > 기본 웹브라우저Safari.app으로 변경한다.

구글링시 아래 기능들을 해제해보라는데 다 쓸모없다.

  • 2차 인증 해제
  • 앱 비밀번호 사용
  • 보안되지 않은 앱 허용

타임존 데이터 최신버전으로 업데이트하기

· 2 min read

타임존 데이터는 php의 버전을 따라 올라가는데, 실무에선 항상 최신버전을 사용하기 쉽지 않다. 그럴 때 데이터만 업데이트하는 방법을 쓸 수 있다.

설치

2019.06 현재 최신 타임존 데이터베이스는 2019a (2019.01) 버전이다.

perl

perl로 설치되는 일반적인 경우는 아래와 같이 쉽게 설치 가능하다.

## perl 로 설치
$ perl install timezonedb

## 끝!

phpize

그렇지 않은 경우 라이브러리를 수동으로 빌드해줘야한다.

## 타임존 데이터 다운로드
$ curl -LO https://pecl.php.net/get/timezonedb-2019.1.tgz

## 압축 해제
$ tar -xvzf timezonedb-2019.1.tgz
$ cd timezonedb-2019.1

## 빌드
$ phpize
$ ./configure --with-php-config=${PHP_CONFIG_PATH}
$ make && make install

## 라이브러리를 extensions 폴더로 이동
$ mv timezonedb.so /usr/local/php/extentions

## 라이브러리 추가
$ vi php.ini
$ extension=timezonedb.so

## 아파치 재시작
$ apachectl restart

## 버전 확인
$ /usr/bin/php -r "echo timezone_version_get();"
2019.01

참조

chrome usb 디버깅 장치를 못 찾거나 연결이 안될 때

· One min read

usb 디버깅 승인 후 chrome://inspect#devices 탭에 접근하면 기기가 보이면서 디버깅이 가능해야하는데, 그렇지 못한 경우에 아래처럼 진행해주면 된다.

해결

먼저 usb 디버깅 권한을 모두 초기화하고 다시 연결한다.

## 디버깅 서버를 죽인 후 다시 실행한다.
$ adb kill-server
$ adb usb

개발자도구의 More tools > Remote devices의 목록에서 확인할 수 있다.

여담

  • PTP 연결을 하라는데 이건 안드로이드 구버전에 대한거라 필요 없다.
  • 웹뷰 디버깅시에는 디버깅 권한이 있는 앱이여야 가능하다.

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에서 lsd 설치하기 (ls 명령어 꾸미기)

· One min read

lsd

ls -al

LSDeluxe 커맨드로 ls 명령어를 예쁘게 변경해보자.

설치

## 설치
$ brew install lsd

## alias 설정
$ vi ~/.zshrc

alias ls='lsd'
alias ll='ls -alhF'

$ source ~/.zshrc

설치는 했지만 아이콘이 깨져서 보일 것이다.

글꼴 설정

## 글꼴 설치
$ brew tap homebrew/cask-fonts
$ brew cask install font-hack-nerd-font

iTerm2 > Preferences > Profiles > Text 탭으로 이동해 Non-ASCII Font를 확장한 뒤 방금 설치한 Hack Regular Nerd Fonr Compelete 폰트를 설정해주자.

Use ligatures 옵션 또한 체크한다.

Non-ASCII Font 설정

nodejs triple des 암호화

· 2 min read

triple des 알고리즘으로 암호화하는 일은 요새는 드문데, 드물어서 그런지 구글링해도 아무 것도 나오지 않았다.

레거시 언어들에는 3des 암호화된 로직이 많은데, 포팅하면서 개발해야될 필요성이 생겼다.

암호화

  • nodejs 의 암호화 패키지는 crypto 안에 들어있다.
  • crypto.getCiphers(); 메소드로 사용할 수 있는 알고리즘을 확인할 수 있다.
    • ciphers.filter(cipher => cipher.includes('des'));
  • 3des 알고리즘은 des-ede3 로 시작한다.
  • 키는 24bytes 이며, iv 는 8bytes 다.

소스

import crypto from "node:crypto";

class TripleDes {
// #iv;
// #key;

constructor(key, iv) {
this.key = key;
this.iv = iv;
}

getKey() {
return this.key.padEnd(24, String.fromCharCode(0));
}

getIv() {
return this.iv;
}

encrypt(plain, iv) {
if (!iv) {
iv = this.iv ? Buffer.from(this.iv, "hex") : crypto.randomBytes(8);
}

const cipher3des = crypto.createCipheriv(
"des-ede3-cfb8",
this.getKey(),
iv,
);
let encrypted = cipher3des.update(plain, "utf8", "hex");
encrypted += cipher3des.final("hex");

this.iv = iv.toString("hex");
return encrypted;
}

decrypt(encrypted, iv) {
if (!iv) {
iv = this.iv;
}

const decipher3des = crypto.createDecipheriv(
"des-ede3-cfb8",
this.getKey(),
Buffer.from(iv, "hex"),
);
let decrypted = decipher3des.update(encrypted, "hex", "utf8");
decrypted += decipher3des.final("utf8");

return decrypted;
}
}

module.exports = TripleDes;

위의 encrypt 메소드는 아래처럼 버퍼를 합친 후에 헥스로 바꾸는 것과 동일하다.

let encrypted = Buffer.concat([
cipher3des.update(plain, "utf8"),
cipher3des.final("utf8"),
]);

encrypted = encrypted.toString("hex");

사용법

const TripleDes = require("./TripleDes");

const tripleDes = new TripleDes("encryptionKey");
// 암호화
const encrypted = tripleDes.encrypt("yummy");
// 복호화
const decrypted = tripleDes.decrypt(encrypted);

참조