변경가능한 커널 설정

노드 레벨의 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.
1
2
3
net.ipv4.neigh.default.gc_thresh1 = 80000
net.ipv4.neigh.default.gc_thresh2 = 90000
net.ipv4.neigh.default.gc_thresh3 = 100000

예시

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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'

여담

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

참조

  • Using sysctls in a Kubernetes Cluster
  • 리눅스 서버의 TCP 네트워크 성능을 결정짓는 커널 파라미터 이야기 - 2편
  • How to increase the ARP cache garbage collection threshold
  • Scaling Kubernetes to 2,500 nodes

앞서

서비스워커로 Navigation Request 나 Static Assets 에 대한 리소스 캐시는 쉽다.
(이전 포스팅 참조)

하지만 POST Request 에 대한 레퍼런스는 찾기 힘들어 결국 만들어버렸다.
복잡한 로직이지만 Request Body 를 SHA1로 해싱해 키로 IndexedDB 에 저장하고 그 키가 맞으면 꺼내주는 방식이다.

소스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
// IndexedDB 는 Promisify 되어있지 않아서 라이브러리가 필요하다.
importScripts(
'https://cdn.jsdelivr.net/npm/localforage@1.7.3/dist/localforage.min.js'
);

// 캐시하고 싶은 POST 엔드포인트
const ENDPOINT = 'https://your-domain/post-request';

const bin2Hex = (buffer) => {
let digest = '';
const dataView = new DataView(buffer);
for (let i = 0, len = dataView.byteLength; i < len; i += 4) {
let value = dataView.getUint32(i);
// hex 로 바꾸면 패딩비트 0 이 제거된다.
let hex = value.toString(16);
// uint32 는 4bytes 로 나온다.
let padding = '00000000';
// 패딩을 더해서 뒤에서 잘라준다.
let paddedValue = (padding + hex).slice(-padding.length);
digest += paddedValue;
}

return digest;
};

const postRequestFetchListener = (fetchEvent) => {
const requestUrl = fetchEvent.request.url;
const method = fetchEvent.request.method.toUpperCase();
// 맞는 엔드포인트인지 확인
if (!(method === 'POST' && requestUrl === ENDPOINT)) {
return;
}

fetchEvent.respondWith(
fetchEvent.request
.clone()
.arrayBuffer()
.then((buffer) => {
const requestBody = String.fromCharCode.apply(
null,
new Uint8Array(buffer)
);
// request body 에 원하는 조건만 캐시처리할 수 있게 한다.
if (requestBody.includes('cache=1')) {
// 속도면에서 다른 해싱 알고리즘을 사용해도 무방하다.
return crypto.subtle.digest('SHA-1', buffer);
}

return Promise.reject();
})
.then((sha1Buffer) => {
const sha1Hash = bin2Hex(sha1Buffer);
console.log('SHA1 Hash => ', sha1Hash);

// IndexedDB 에서 캐시된 키를 찾는다.
return localforage.getItem(sha1Hash).then((cachedResponse) => {
if (cachedResponse) {
console.log('Cached repsonse => ', cachedResponse);
return new Response(cachedResponse, {
// 여기서 statusCode 를 304 로 내보내고 싶었으나, Body 를 반환할 수 없었다.
status: 200,
statusText: 'OK',
headers: {
'Content-Length': cachedResponse.length,
'Content-Type': 'application/json',
// 그래서 커스텀 헤더를 추가했다.
'X-SW-Cache-Hit': 1,
'X-SW-Cache-Type': 'POST',
},
});
}

// 캐시된 데이터가 없을 경우 새로 요청한다.
return fetch(fetchEvent.request).then((response) => {
console.log('Fetching response => ', response.clone());

// 정상적일 경우만 IndexedDB 에 저장한다.
if (200 <= response.status && response.status < 400) {
// 이 작업은 비동기지만 굳이 기다리지 않아도 된다.
response
.clone()
.text()
.then((textResponse) => {
console.log('Caching response => ', textResponse);
return localforage.setItem(sha1Hash, textResponse);
});
}

return response;
});
});
})
.catch(() => fetch(fetchEvent.request))
);
};

// 리스너를 등록해준다.
self.addEventListener('fetch', postRequestFetchListener);

여담

  • WorkBox 를 사용할 수 있다면 CacheableResponse와 CacheFirst 정책으로 단번에 처리 가능할 것이다.
  • 굳이 해시를 키로 사용하지 않아도 된다. RequestBody 의 Serialize 를 키로 써도 된다. (만들면서 crypto 라이브러리를 사용해보고 싶었을 뿐)

인스트럭션

  • 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과 같은 빌드가 필요한 이미지에서는 멀티스테이지 빌드를 이용해 빌드 환경과 프로덕션 환경을 다르게 가져갈 수 있다.

1
2
3
4
5
6
7
8
9
10
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 취약점도 업데이트 된다고 한다.
1
2
3
4
5
6
7
8
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 인스트럭션으로 추가 된 파일은 해시기반 체크섬 검증을 해주는 것이 좋다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
1
2
3
4
5
.git
.idea
.vscode
.github
*.log

이미지 테스트

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

  • container-structure-test
  • goss

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

이미지 보안

user

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

1
2
3
4
5
6
7
8
9
FROM golang:1.10

RUN mkdir /app
COPY main.go /app

RUN useradd gracefullight
USER gracefullight

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

secret

  • Vault

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년 최근까지 개선 중

관리 원칙

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

여러 맥 기기의 환경을 구성하다보니 적어 놓는 게 나을 듯 싶었다.

brew

1
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

formulae

main formulae

1
2
3
4
5
6
7
8
9
10
11
12
brew install azure-cli \
fzf \
git \
go \
java \
kubernetes-cli \
mas \
node \
python \
stern \
tree \
wget
  • azure-cli
  • fzf
  • git
  • go
  • java
  • kubernetes-cli
  • mas
  • node
  • python
  • stern
  • tree
  • wget

sub formulae

docker for mac

1
brew tap pivotal/tap
1
2
3
4
5
6
7
8
9
10
brew install anaconda \
dep \
helm \
kubectx \
springboot \
thefuck \
yarn \
youtube-dl \
zsh-autosuggestions \
zsh-syntax-highlighting
  • anaconda
  • dep
  • helm
  • kubectx
  • springboot
  • thefuck
  • yarn
  • youtube-dl
  • zsh-autosuggestions
  • zsh-syntax-highlighting

cask

1
2
brew tap homebrew/cask-fonts
brew tap adoptopenjdk/openjdk
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
brew cask install adguard \
adoptopenjdk11 \
battle-net \
bitwarden \
cheatsheet \
cleanmymac \
firefox \
flux \
font-cascadia \
font-hack-nerd-font \
fork \
google-chrome \
haptickey \
hiddenbar \
iina \
iterm2 \
java \
jetbrains-toolbox \
keepingyouawake \
keka \
kitematic \
mattermost \
microsoft-edge \
pock \
sequel-pro \
slack \
telegram-desktop \
udeler \
yt-music
  • adguard: 💰
  • adoptopenjdk11: 자바
  • battle-net: 배틀넷
  • bitwarden: 비밀번호 관리
  • cheatsheet: ⌘ 키를 오래 누르면 해당 프로그램의 모든 단축키를 볼 수 있음
  • cleanmymac: 💰
  • firefox: 파이어폭스
  • flux: 블루라이트 차단
  • font-cascadia: Cascadis Code 폰트
  • font-hack-nerd-font: iTerm2 용 터미널 폰트
  • fork: 무료 중 최고의 git client
  • google-chrome: 크롬
  • haptickey: 터치바 반응성 추가
  • hiddenbar: 작업표시줄의 프로그램 숨기기
  • iina: 깔끔한 인터페이스의 미디어 플레이어
  • iterm2: 터미널
  • jetbrains-toolbox: jetbrains IDE 버전 관리
  • keepingyouawake: 잠자기 모드 제어
  • keka: 압축 프로그램
  • kitematic: 컨테이너 쇼핑
  • mattermost: 매터모스트
  • microsoft-edge: edge
  • pock: 터치바를 Dock으로 만들어줌
  • sequel-pro: MySQL GUI client
  • slack: 슬랙
  • telegram-desktop: 텔레그램
  • udeler: udemy 강의 다운로더
  • yt-music: 유튜브 뮤직 플레이어

App store

1
2
3
4
5
6
7
8
9
10
11
12
mas install 497799835 \
1355679052 \
869223134 \
441258766 \
1295203466 \
1274495053 \
1289197285 \
1471801525 \
897118787 \
1176895641 \
425424353 \
1475628500
  • xcode: 497799835
  • dropover: 1355679052 드래그 후 흔들면 가상폴더로 저장
  • kakaotalk: 869223134 카카오톡
  • magnet: 💰 441258766 창 크기 전환
  • microsoft remote desktop: 1295203466 윈도우 원격
  • microsoft todo: 1274495053 todo 관리
  • mindnode: 1289197285 마인드맵
  • polyglot: 1471801525 사파리 번역기
  • shazam: 897118787 음악 찾기
  • spark: 1176895641 최고의 mail client
  • unicorn https: 1475628500 Encrypt DNS query

개선

  • bundle 처리

Deployment

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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 를 설정해주는 것이 좋다.

1
2
3
4
5
6
spec:
# 기본값 30
terminationGracePeriodSeconds: 40
containers:
- name: maria
image: maria:latest

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

1
2
3
4
5
6
7
8
9
10
11
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 교체 시에는 어떤 전략을 가져가야할까
답은 아래와 같다.

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

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

1
2
3
4
5
6
7
8
9
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 값을 라벨에 따라 변경하자.
  • 서비스메시를 연동하면 카나리아 배포 방식도 가능하다.

The Basics

Types

  • 아무 것도 없는 타입은 None
  • 주석
    • #
    • """ """
  • 포매팅
    • f’test {변수}'
    • "test {}".format(변수)
  • list, dict, set 등은 immutable 하지 않아 copy 메소드를 사용해 복사 필요
  • 스왑
    • a, b = b, a 로 한 줄로 가능
  • set
    • {} 으로 set 지정가능
    • 집합연산 가능
    • set([]) 로 배열을 set으로 변환 가능
  • enum
    • 튜플로 지정하면 됨
    • 튜플은 freeze 되어 있다.
    • ('a', 'b', 'c')
  • list
    • merge는 더하기로 가능 [] + []

Operaters

  • if
    • 값 체킹에는 0.0 '' [] () {} set() 등 모든 빈 값이 false 로 떨어진다.
      • 따라서 len(foo) > 0 보다 권장
    • if foo != True 보다 if not foo 를 권장
    • dict 키 검증 시에는 if 'key' in d: 를 권장 if ‘key’ in d.keys(): 필요없다.
    • y=None x = 1 if y else 2 면 x=2 라는 문법이 된다.
  • is 문은 값이 None 인지 확인할 때 권장
    • if foo == None 보다 if foo is None 을 권장
    • if foo is not None
  • while
    • while else 구문 가능
      else 는 while 에 break 가 없을 경우 while 끝나고 실행
  • for
    • for in
    • for in else 구문 가능
      else는 for 에 break 가 없을 경우 for 끝나고 실행
    • for _ in range(10) 처럼 underscore 는 index 를 안쓸때 권장
    • for i, item in enumerate(['a', 'b', 'c']) 처럼 index 넣어줄 수 있음
    • for day, fruit in zip(days, fruit) 처럼 패킹(zip) 함수 기본 제공
    • for k, v in d.items() 로 dict 타입 반복 가능
  • func
    • def func(): 로 함수선언
    • def add(a: int, b: int) -> int: 처럼 파라미터 및 리턴 타입 정의 가능
      • compile error 발생 안 함
      • 보여주기 위한 타입 기능
    • 매개변수를 위치 인수로 넣을 수 있음
      • def menu(entree, drink)라면 menu(drink='a', entree='b') 로 호출 가능
    • list 매개변수는 기본값으로 넣으면 안 됨 def foo(list=[]) 면 리스트가 한 번만 생성됨 (함수 내부 초기화 필요)
    • def func(*args) 로 동적매개변수 처리 가능 튜플로 처리됨
    • def func(**kargs) 로 넣으면 dict로 전달가능 func(key1='val', key2='val')
    • 두 구문을 같이쓸 수 있지만 *args**kargs 보다 먼저 와야한다.
    • 함수 내부 """ """ doc 주석을 넣으면 help(func.__doc__), help(func) 로 도움말 호출 가능
    • 클로져로 wrapper 함수를 만들면 데코레이터로 바로 적용 가능
    • func(lists, lambda item: item.value()) 람다 처리가능 (value: returnValue)
  • 비추천 dic = {x: y for x, y in (zip(w, f)} 처럼 한 번에 for문 dictionary 선언 가능
  • genarator
    • def + yield
    • gen = (i for i in range(10) if i % 2 == 0) 처럼 한 번에 선언하면 제네레이터가 된다.
    • tuple(gen) 하면 튜플로 처리된다.
    • for 보다 빠를 수 있다.
  • globals(), locals() 로 전역, 로컬변수 확인가능
  • excpetion
    • try: except IndexError as ex: finally:
    • try: except: else: finally: 가 있다면 try => else => finally 로 실행
    • raise IndexError('test error') 처럼 raise 로 에러 발생 가능
    • 기본에러는 exception hierarchy 참조

Package

  • __init__.py 가 있어야한다.
  • import package.utils 또는 from package import utils 해서 utils.func 롤 호출
  • from package import utils as NamedUtil 처럼 as 문으로 네임스페이스 변경 가능
  • 비추천 from package.folder import * 로 folder 레벨의 모든 python import 가 가능한데
    folder.__init__.py__all__ = ['py', 'py2'] 처럼 선언해줘야한다.
  • setup.py 를 만들어 패키지를 배포시킬 수 있다.
    • PyCharm: Tools > Create setup.py > run setup.py > sdist 로 출력
    • Cli: python setup.py sdist
  • PyPI 에 서드파티 라이브러리를 등록하면 pip install termcolor 처럼 설치 가능
  • import 라이브러리 순서는 위에서부터 표준, pip, 로컬패키지, 로컬파일
  • if __name__ == '__main__': main()entrypoint 스크립트에서 사용되는 패턴이다.

Class

  • 생성자 __init__(self):, Instance() 로 new 없이 생성
  • 소멸자 __del__(self):, del instance 로 삭제
  • class method 의 첫 인자로는 self를 받아야 this처럼 사용이 가능하다. 두 번째 부터 파라미터를 받을 수 있다.

protected

  • proteced 변수는 _var 처럼 underscore 하나를 넣는다.
  • @property, @property_name.setter 데코레이터를 통해 getter/setter 처리가 가능하다.
1
2
3
4
5
6
7
8
9
10
11
class Instance(object):
@property
def foo(self):
return self._foo

@foo.setter
def foo(self, bar):
self._foo = bar

intance = Instance()
instance.foo = bar

private

  • private 변수는 self.__foo 로 underscore 두 개를 넣는다.
  • 인스턴스 밖에서 접근이 불가능하다.

class variable

  • 클래스 변수는 모든 오브젝트 초기화시에 공유되므로 list, dict… 등을 사용하지 않아야한다.
  • 상수처럼 쓰는게 좋을 듯

class method

  • class 메소드는 @classmethod 데코레이터로 가능하다.
1
2
3
4
5
6
7
class Instance(object):
@classmethod
def foo(cls):
# cls 로 클래스 접근가능
return cls.bar

Instance.foo()

static method

  • static 메소드는 @staticmethod 데코레이터로 정의할 수 있으나 잘 사용되지는 않는다.

상속

1
2
3
4
5
6
7
class Parent(object):
def __init__(self):
pass

class Child(Parent):
def __init__(self):
super().__init__
  • 다중상속 하지말자
  • class Twins(Parent, Parent2) 로 되지만 메소드명이 같을 경우 왼쪽에 선언된 것만 실행된다.

추상

1
2
3
4
5
6
7
8
9
10
import abc

class Parent(metaclass=abc.ABCMeta):
@abc.abstractmethod
def foo(self):
pass

class Child(Parent):
def foo(self):
print('foo')

특수 메소드

  • 많지만 __str__ 이 제일 많이 쓴다. toString() 과 같다.
  • __len__ (len(instance))
  • __eq__ (instance == instance2)
  • __add__ (instance + instance2)
  • 등등…
  • 클래스 기본기능을 해치는 개인적인 느낌

여담

  • MSA의 시대에 살고 있는 어플리케이션 레이어의 개발자는 어쩔 수 없이 폴리글랏 프로그래머가 되기 마련이다.
  • 하나의 언어에 능통하면 다른 언어로 넘어가는 데에는 익숙함의 문제지만,
    그 언어를 제대로 사용하기 위해 가장 중요한 건 스타일 가이드, 린팅과 주기적인 Docs, Release Notes 확인이라고 생각한다.

Timestamp

Carbon으로 타임스탬프를 파싱하는 데에는 createFromTimestamp 메소드가 있다.

1
Carbon::createFromTimestamp(1576249805)->format();

하지만 더 쉽게 parse 메소드를 사용해 파싱할 수도 있다.

1
2
$timestamp = 1576249805;
Carbon::parse('@' . $timestamp)->format();

여기서의 @는 오류를 무시하는 기분이 들어서 찾아보았는데 표준이였다.

Example #2 DateTime::setTimestamp() alternative in PHP 5.2

1
2
3
4
$ts = 1171502725;
$date = new DateTime("@$ts");
echo $date->format('U = Y-m-d H:i:s') . "\n";
?>

참조

  • timestamp 는 UTC 기준이며 (1970년부터의 차이) timezone 을 정의할 수 없다.
  • php man: datetime.settimestamp

TS5071

node 에서 즐겨쓰는 package.json import 방법은 아래와 같다.

1
2
import packageJson from '../package.json';
console.log(packageJson.version);

편안하게 잘 사용되는 로직인데 타입스크립트로 변경시에는 몇 가지 설정을 해줘야한다.
설명에 필요없는 설정은 생략했다.

tsconfig.json
1
2
3
4
5
6
7
{
"compilerOptions": {
"moduleResolution": "node",
"resolveJsonModule": true,
"esModuleInterop": true
}
}

또는 tsc 실행시에 --esModuleInterop, --resolveJsonModule 옵션을 추가해 빌드해줘야한다.

참조

  • https://github.com/microsoft/TypeScript/issues/26224
  • https://github.com/microsoft/TypeScript/pull/26825/files

TS7053

3.0 버전이 되면서 HOC 기반으로 변경되며 rules를 상위 컴포넌트에서 확장하게 되었다.
문제는 typescript 기반에서 룰 전체 등록이 TS(7053) 에러를 발생시킨다.

1
2
3
4
5
6
import { ValidationProvider, ValidationObserver, extend } from 'vee-validate';
import * as rules from 'vee-validate/dist/rules';

Object.keys(rules).forEach((rule) => {
extend(rule, rules[rule]); // rules[rule] 에서 타입오류 발생
});
1
2
Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'typeof import(".../node_modules/vee-validate/dist/rules")'.
No index signature with a parameter of type 'string' was found on type 'typeof import(".../node_modules/vee-validate/dist/rules")'.ts(7053)

원인

import, export 의 모듈명은 string index 로 쳐지지 않아 발생한다.

해결방법

Object.entriesfor of를 사용해 타입에 안전하게 돌려주면 된다.

1
2
3
4
5
6
7
8
import { extend } from 'vee-validate';
import * as rules from 'vee-validate/dist/rules';

for (let [rule, validation] of Object.entries(rules)) {
extend(rule, {
...validation,
});
}

여담

  • 새로운 구문 (async/await, import/export)를 사용해 돌릴 땐 먼저 for of를 사용하는 습관을 들여야겠다.
  • 머지되어서 다음 사람의 삽질은 없을 듯 하다.

참조

  • Merged docs: added Installing All Rules with typescript
  • vee-validate#installing-all-rules