← today i learned

TIL Archive

TIL 아카이브 — Today I Learned

2016-2026년 kil9/til 저장소에 쌓인 기술 노트 22편을 토픽별로 이관한 열람용 아카이브. 짧은 트러블슈팅과 발견의 기록이며, 내용은 작성 시점 기준이다.

Docker

Docker Internals

docker/internals.md

http://docker-saigon.github.io/post/Docker-Internals/ 의 요약/번역

리눅스 컨테이너 요약 — 컨테이너란 무엇인가?

  • 컨테이너는 호스트 커널을 공유한다.
  • 컨테이너는 리소스 제어를 위해 프로세스를 그룹화하는 커널의 능력을 활용한다.
  • 컨테이너는 네임스페이스들 간의 격리를 제공한다.
  • 컨테이너는 경량의 VM처럼 느껴질 수 있으나 실제로 가상머신인 것은 아니다.

컨테이너의 생태계는 다음과 같은 구성요소를 포함한다: 런타임, 이미지 배포, 툴링(Tooling). Reference slide deck

컨테이너와 패키지 매니저의 차이?

이미지로 패키징하는 것은 RPM과 비슷하지만, 리눅스 배포본(소프트웨어가 제대로 패키지되어있기 어려운)과 분리되어 있다. Docker의 큰 혁신은 패키지 매니저보다 좀 더 사용하기 쉽다는 것이다. 패키지 매니저는 공유 라이브러리의 버전이 다르면 의존성 이슈를 일으켜 실패하는데, 이것은 공유 라이브러리를 이미지 내에 패키징하는 것으로 해결한다.

패키지 매니저들은 패키지 내부에 무엇이 들어있는지 파악할 쉬운 방법을 제공한다. 컨테이너 이미지를 다루는 방법에 대해서는 Dockercon EU talksShipping Manifests, Bill of Lading and Docker Metadata and Containers — Video를 참고한다.

컨테이너와 구성 매니지먼트의 차이?

유명한 CM(Configuration Management) 도구로 Puppet(Ruby), Chef(Ruby), Ansible(Python), SALT(Python), Terraform(Golang) 등이 있다. 위의 몇몇 도구들은 절차적으로 환경을 구성하는데, self-contained하고 동일한 환경의 동일한 아키텍쳐에서 정확히 같은 방식으로 실행되는 패키지를 배포하는 것과는 다르다. 하지만 Docker 환경을 설치할 때 쓰는건 좋다. 이후에는 컨테이너 런타임 레이어가 어플리케이션 레이어를 관리하게 한다.

  • Docker는 self-contained 이미지를 제공하며 클라우드에서나 랩탑에서나 정확히 동일하게 실행된다('Immutable Infrastructure').
  • Docker는 매우 빠르며, 컨테이너를 띄우는데 수 초면 된다. 오버헤드도 매우 작다.
  • Dockerfile DSL은 매우 간단하고 강력하다.

왜 Docker인가?

Docker 생태계는 현재 아래 패키지들을 모두 제공하는 유일한 생태계이다: Image management, Resource Isolation, File System Isolation, Network Isolation, Change Management, Sharing, Process Management, Service Discovery (DNS since 1.10).

어떻게 동작하는가? — 커널 네임스페이스

하이레벨 설명은 공식 문서를 볼 것.

  • Process trees (PID Namespace)
  • Mounts (MNT namespace) wc -l /proc/mounts
  • Network (Net namespace) ip addr
  • Users / UIDs (User Namespace)
  • Hostnames (UTS Namespace) hostname
  • Inter Process Communication (IPC Namespace) ipcs — IPC를 쓰는 예시 = PostgreSQL

Cgroups

커널 제어 그룹(cgroups)을 통해 프로세스에 의해 사용된 리소스들의 관리를 할 수 있고, 장치 노드들에 대한 약간의 접근제어, 혹은 프로세스 그룹의 프리징 등을 제공한다. cgroups는 리소스들에 대한 하나의 계층구조(트리)를 제공한다. 아래는 하나의 예시이다.

cpu                      memory
├── batch                ├── 109
│   ├── hadoop           ├── 88 <
│   │   ├── 88 <         ├── 25
│   │   └── 109          ├── 26
└── realtime             └── databases
    ├── nginx                ├── 1008
    │   ├── 25               └── 524
    │   └── 26
    ├── postgres
    │   ├── 524
    └── redis
        └── 1008

각각의 계층구조에 서브그룹을 만들 수 있다. 위의 예시에선 cpu 리소스 아래에 batchrealtime 서브그룹을 만들었다. 각각의 리소스에 대해 개별 프로세스가 1개의 노드가 된다. 예를 들어 pid 88은 cpu에도 메모리에도 있다.

(계속)

Docker Hub / Private Registry

docker/private_registry.md

Docker Hub는 docker 이미지들을 서빙하는 웹서비스. docker는 특정 이미지를 run 시킬 경우 로컬에서 해당 이름을 가진 이미지를 찾아보고 없으면 자동으로 Docker Hub에서 검색해서 받아온다. Docker Hub는 Dockerfile만 올려두면 자동으로 빌드를 하기도 하고 webhook을 달 수 있는 등의 기능도 가지고 있다.

Docker private registry

Docker Hub을 이용할 수 없는 환경이라면 로컬에서 Docker 이미지를 서빙할 수 있다. 이 서버 또한 Docker 이미지로 되어있음. registry. Docker를 이용하여 레지스트리 서버를 띄울 수 있다. registry:2 이용. 2는 버전.

docker run -d -p 5000:5000 --restart=always --name registry registry:2

그리고 여기에 tagging한 이미지를 push하면 자동으로 서빙하게 된다.

docker tag cana/cana-standalone localhost:5000/cana/cana-standalone
docker push localhost:5000/cana/cana-standalone

가져올 때는 이렇게

docker pull localhost:5000/cana/cana-standalone

정지하려면 이렇게

docker stop registry && docker rm -v registry

외부에서도 접근이 가능한 private registry를 만들기 위해서는 아래 방식을 참고한다.

자동으로 생성되는 docker.service에서는 별도로 EnvironmentFile을 읽지 않기 때문에, 이를 추가하여 환경변수를 설정해주고, docker.service를 수정하여 직접 이 옵션을 호출해주어야 한다. docker.service 파일을 다음과 같이 수정한다. EnvironmentFile 행을 추가하고, ExceStart 행은 수정한다. /etc/sysconfig/docker 파일에는 필요한 옵션들을 지정한다.

[Service]
EnvironmentFile=-/etc/sysconfig/docker
...
ExecStart=/usr/bin/docker daemon -H fd:// $DOCKER_OPTS
...

/etc/sysconfig/docker 파일을 만들고 다음의 내용으로 저장한다.

DOCKER_OPTS="--insecure-registry={HOSTNAME}:{PORT}"

참고: registry/deploying · registry/insecure · engine/admin/systemd

root 없이 실행시키기

docker/rootless.md

group을 만들고 거기에 user를 추가해주면 됨

sudo groupadd docker
sudo usermod -aG docker $USER

socket 권한이 없다고 뭐라고 하는 경우

sudo setfacl -m user:$USER:rw /var/run/docker.sock

https://askubuntu.com/a/982187

Unable to enable SKIP DNAT rule 뜰 경우

docker/ts.md

docker를 재시작하면 된다

↑ 목차로

Vim

외부 vimrc 로드

vim/vimrc.md

vim 시작시 자동으로 외부 vimrc 파일을 로드하려면 메인 .vimrc 파일에 set exrc가 들어있어야 한다.

증가하는 숫자 붙여넣기·치환·zero padding

vim/increasing_numbers.md

증가하는 숫자 붙여넣기

:put =range(11,15)

출력:

11
12
13
14
15

증가하는 숫자 치환하기

:let i=1 | g/abc/s//\='xyz_'.i/ | let i=i+1

입력:

this is abc
that is abc
we are abc

출력:

this is xyz_1
that is xyz_2
we are xyz_3

zero padding 넣기

:let i=1 | g/XXX/s//\=printf("%03d", i)/ | let i=i+1

YouCompleteMe 트러블슈팅

vim/ycmd.md

CMake 버전이 낮아서 빌드가 안될 때

devtoolset-8

sudo yum install centos-release-scl
sudo yum-config-manager --enable rhel-server-rhscl-7-rpms
sudo yum install devtoolset-8
scl enable devtoolset-8 bash

https://stackoverflow.com/a/68679386

수동으로 빌드하기

cd $HOME/.vim/plugged/plugged/youcompleteme
python3 ./install.py --go-completer --ts-completer

vim-go GoDef 안 먹힐 때

vim/vim-go.md

vim-go에서는 별도의 바이너리들을 활용하는데, 일부 바이너리는 모듈을 제대로 지원하지 않는 경우가 있음.

let g:go_def_mode = 'godef'

↑ 목차로

Go

go.mod

go/go_mod.md

  • 다른 module의 go.mod 의 replace 부분은 무시된다
  • 예를 들어, 빌드하려는 모듈이 A 모듈이고 B 모듈을 import할 경우 B 모듈의 go.mod 파일에 적힌 replace는 무시된다.
  • 따라서 A모듈에도 replace 절을 똑같이 적어주어야 함.

reflect — 구조체 순회

go/reflect.md

package main

import (
        "fmt"
        "reflect"
)

func main() {
        x := struct {
                Foo string
                Bar int
        }{"foo", 2}

        e := reflect.ValueOf(&x).Elem()

        for i := 0; i < e.NumField(); i++ {
                v := e.Field(i)
                t := e.Type().Field(i)
                fmt.Printf("name: %s, type: %s, value: %v, tag: %s\n",
                        t.Name, t.Type, v.Interface(), t.Tag.Get("custom"))
        }
}

↑ 목차로

Python

Matrix Multiplication

python/matrix_multiplication.md

파이썬(>=3.5.0)은 matrix multiplication 연산자 @을 지원한다. 아래는 PEP465에 첨부된 예제이다.

import numpy as np
from numpy.linalg import inv, solve

# dot 함수를 이용할 경우
S = np.dot((np.dot(H, beta) - r).T,
           np.dot(inv(np.dot(np.dot(H, V), H.T)), np.dot(H, beta) - r))

# dot 메서드를 이용할 경우
S = (H.dot(beta) - r).T.dot(inv(H.dot(V).dot(H.T))).dot(H.dot(beta) - r)

# @ 연산자를 이용할 경우
S = (H @ beta - r).T @ inv(H @ V @ H.T) @ (H @ beta - r)

@ 연산자를 쓰는 쪽이 간결하다. 참고: PEP465

Flask-SQLAlchemy에서 join된 칼럼들에 대해 paginate() 사용하기

python/flask-sqlalchemy_paginate_hack.md

Flask-SQLAlchemy는 paginate 클래스를 제공하지만 join된 칼럼에 대해서는 직접 사용할 수 없다. join한 후 하나의 테이블에 대한 결과만 필요하다면 상관없지만, 두 개의 테이블에 대한 결과를 모두 사용해야 할 때는 원래의 BaseQuery를 써야 하고 이 때는 paginate를 사용할 수 없는 것.

해결하려면 아래처럼 pagination을 래핑하거나,

def paginate(query, page, per_page=20, error_out=True):
    if error_out and page < 1:
        abort(404)
    items = query.limit(per_page).offset((page - 1) * per_page).all()
    if not items and page != 1 and error_out:
        abort(404)

    # No need to count if we're on the first page and there are fewer
    # items than we expected.
    if page == 1 and len(items) < per_page:
        total = len(items)
    else:
        total = query.order_by(None).count()

    return Pagination(query, page, per_page, total, items)

아래처럼 내용물을 바꿔치기할 수 있다.

from flask.ext.sqlalchemy import BaseQuery
def paginate(sa_query, page, per_page=20, error_out=True):
    sa_query.__class__ = BaseQuery
    # We can now use BaseQuery methods like .paginate on our SA query
    return sa_query.paginate(page, per_page, error_out)

두 번째 방법은 위험할 수 있으니 중요한 곳에서는 사용하지 말 것. 참고: stackoverflow

OpenSSL 문제로 안 깔릴 때

python/install-problems.md

패키지 매니저로 설치해도 잘 인식하지 못하는 경우 sudo 권한이 있다면 그냥 직접 설치한다.

./config shared --prefix=/usr
make
sudo make install

/usr/local/bin 등을 인식하지 못하는 경우에는 아예 /usr에 덮어써버리면 인식은 잘 됨

↑ 목차로

CMake

설치할 특정 디렉토리를 지정하기

cmake/install-to-user-directory.md

cmake -DCMAKE_INSTALL_PREFIX:PATH=~/testinstall -DCMAKE_BUILD_TYPE:STRING=Release .

visitusers.org — Installing using CMake

↑ 목차로

Git

Git에서 특정 ssh key를 쓰고 싶을 경우

git/specify_ssh_key.md

~/.ssh/config 파일에 다음 내용을 추가한다.

Host github.com
  IdentityFile ~/.ssh/github_rsa

http://stackoverflow.com/a/18839540

↑ 목차로

GitHub Actions

Rerun은 기존 Pod를 재사용한다

github-actions/rerun-uses-existing-pod.md

GitHub Actions에서 job을 rerun하면 새 Pod를 생성하지 않고 기존 Pod를 그대로 사용한다. 따라서 Actions Runner를 새 버전으로 배포한 뒤 rerun해도, 이미 실행 중이던 Pod가 살아있으면 예전 Runner Pod에서 실행된다.

해결 방법

새 Runner가 확실히 사용되도록 하려면 rerun 대신 새 커밋을 push하거나 PR을 다시 트리거해야 한다.

↑ 목차로

Node.js

node.js의 GLIBC 최신 버전 의존성 문제

node/glibc.md

  • node.js 18+ 버전은 GLIBC 최신 버전이 필요함
  • 하지만 CentOS 7에서 GLIBC 업그레이드를 하는 것은 큰 리스크가 있음. glibc의 활용도가 높기 때문에 많은 모듈들이 하드 링크를 하고 있기 때문이라고 함.
  • 별도로 빌드해서 node에서만 쓴다고 해도 빌드 자체가 쉽지 않음.
  • GLIBC 2.17 버전에서 동작하는 node.js를 찾으면 됨.

↑ 목차로

Octave

urlread() 버그

octave/urlread_bug.md

특정 버전(4.2.0)의 Octave에는 urlread() 버그가 있다.

responseBody = urlread(submissionUrl, 'post', params);

위 부분을 아래처럼 바꿔 우회할 수 있다.

[code, responseBody] = system(sprintf('echo jsonBody=%s | curl -k -X POST -d @- %s', body, submissionUrl));

위 코드는 Coursera ML 강의 중 2번째와 그 이후 과제에 포함되어있다. 수정하지 않으면 submit()할 때 Peer certificate cannot be authenticated with given CA certificates 에러가 발생하게 됨.

http://stackoverflow.com/a/38559677

↑ 목차로

PostgreSQL

트러블슈팅

postgres/troubleshoot.md

테이블 설명시 아래와 같은 에러가 나올 경우

flowermap=> \d flower_map_items
ERROR:  column c.relhasoids does not exist
LINE 1: ..., c.relhasindex, c.relhasrules, c.relhastriggers, c.relhasoi...
                                                             ^

오래된(1.11 미만) postgres client를 쓰기 때문에 발생. postgres client를 업데이트해줘야 함.

↑ 목차로

tmux

New window 스크립트

tmux/tmux-split-script.md

tmux에서 새로운 window를 만들기 위한 스크립트는 대략 다음과 같이 만들 수 있다.

#!/bin/bash

NAME='window-name'
HOSTS=(
server01
server02
server03
)

for idx in ${!HOSTS[@]}; do
  if [ $idx -eq 0 ]; then
    tmux new-window "ssh ${HOSTS[idx]}"
  else
    tmux split-window "ssh ${HOSTS[idx]}"
    tmux select-layout tiled
  fi
done
tmux set synchronize-panes on
tmux rename-window "$NAME"

↑ 목차로

zsh

Ctrl-R 안될 때

zsh/ctrl-r.md

$HOME 디렉토리의 .fzf.bash 파일을 .fzf.zsh 로 바꿔주고 내용도 bash를 zsh로 변경한다.

# Auto-completion
# ---------------
[[ $- == *i* ]] && source "~/env/fzf/shell/completion.zsh" 2> /dev/null

# Key bindings
# ------------
source "~/env/fzf/shell/key-bindings.zsh"

↑ 목차로

부록

.vimrc — TIL 카운트 매크로

.vimrc

원 저장소 루트에 있던 로컬 vimrc. jbranchaud/til 에서 가져온 것으로, README 인덱스의 노트 개수를 세는 매크로다. 메인 .vimrcset exrc 가 있어야 로드된다(위 외부 vimrc 로드 노트 참조).

function! CountTils()
  execute '%s/^- \[//n'
endfunction

nnoremap <leader><leader>c :call CountTils()<cr>

↑ 목차로