리눅스에서의 IPC Socket vs Network Socket

Unix Domain Socket은 리눅스 상에서 동작하는 클라이언트-서버 프로세스로 나뉜 소프트웨어에서 자주 사용된다. 예를 들어, Docker에서는 기본 설정으로 TCP Socket이 아닌 Unix Domain Socket으로 dockerd와 docker가 통신한다. 이 경우 외부에서 접속할 수 없다.

비슷하게 MySQL client도 host=”localhost”인 경우 Unix Socket을 사용한다.

이 글은 Unix Domain Socket이란 무엇이고, 그 사용 예와 특징에 대해 서술한다.


1. Socket의 종류

POSIX(리눅스/유닉스)에는 소켓이 2 종류가 존재한다.

  1. IPC Socket
  2. Network Socket

여기서 Network Socket은 IP Socket, 즉 TCP, UDP 통신에 쓰이는 소켓이며, IPC Socket은 오늘의 주제인 Unix Domain Socket을 의미한다.


2. Unix Domain Socket

[StackOverFlow]
[Wikipedia]

Unix Domain Socket, 줄여서 UDS는 IPC의 종류 중 하나로, TCP, UDP에 대응되는 API (SOCK_STREAM, SOCK_DGRAM)가 있기 때문에 소켓의 file descriptor를 할당 받는 구문만 변경하면 네트워크 소켓을 썼을 때와 같은 코드를 사용할 수 있게 된다.

1
2
3
4
5
6
7
8
9

// sockaddr 대신 socketaddr_un 구조체 사용
// 이 구조체는 내부 필드 sun_path에 UDS의 경로를 입력받는다. (포트 대신에!)
struct sockaddr_un addr;

// 네트워크 소켓이라면 AF_INET을, UDS 라면 AF_UNIX 를 사용한다.
if ((client_sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
//...
}

자세한 UDS 코드에 대해선 간결한 설명은 이 블로그를 참고하고, 자세한 설명은 이 블로그를 참고하면 좋을 것 같다.


UDS를 사용할 경우 하부의 네트워크 프로토콜을 거치지 않고 OS 커널만을 통해서 통신이 이루어진다. 따라서 UDP를 사용한 코드더라도 UDS를 쓴다면 항상 순서가 보장되는 등 네트워크의 불확실성이 모두 제거된다.

참고로 IPC의 경우 UDS보다 더 빠른 방법들이 많다 - 가장 빠른 것은 shared memory 방법이라고 한다 [IPC 성능 측정에 대한 글]. 해당 링크를 따라가면 알겠지만 IPC 방법 중에는 메모리 복사를 줄일 수 있는 방법이 많은 데 비해 UDS가 가장 많은 메모리 복사를 하는 것을 알 수 있다.


UDS는 리눅스의 파일 권한을 따르므로, 통신에 대한 권한을 제어할 수 있다.

Linux는 소켓도 파일로 관리하는데, UDS는 파일 속성의 첫 글자가 srwxrwxrwx와 같이 s이다. Network 소켓도 소켓이기 때문에 파일이긴 하지만, UDS는 파일 이름이 있고, Network Socket은 파일 이름이 없어서(unnamed) 포트로 접근해야 한다. UDS 소켓 코드를 보면, UDS 소켓의 경로를 알고, 해당 파일에 대한 권한이 있으면 해당 소켓으로 통신을 수행할 수 있다. 따라서 해당 소켓 파일에 대한 접근 권한을 통해 통신 권한도 조절할 수 있다!


TODO

  1. Socket 프로그래밍 해보고 실제 코드 차이에 대해 더 뜯어보기

  2. loopback IP에서의 overhead 비교해보기

  3. 다른 IPC 메커니즘과 성능 비교해보기


ZSH를 WSL2에 설치하는 방법

이 글은 WSL2 환경에서 zsh 및 주요 플러그인을 설치하는 것을 다룬다.

zsh는 주로 맥을 쓰는 개발자들은 항상 쓰는 그 셸이라고 생각하면 된다.

기준 환경: WSL 2 (Windows 10 2004)


1. zsh, oh-my-zsh 설치

1
2
3
4
$ sudo apt install zsh

# oh-my-zsh 설치
$ sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"

참고로 Oh My Zsh는 zsh 설정을 관리하는 도구로, 주로 플러그인 설치 시 매우 깔끔히 되게 해준다.


2. zsh 테마 설정: agnoster

그냥 zsh는 별로이다. agnoster 테마가 바로 그 ‘익숙한 테마’이므로, 해당 테마를 사용하도록 설정한다. 해당 테마는 특수한 폰트를 사용하므로 설치해준다.

설치를 위해 Powershell을 관리자 권한으로 실행한다.

1
2
3
4
5
6
7
# Powershell
$ git clone https://github.com/powerline/fonts.git

$ cd fonts

# 자동으로 폰트 묶음을 설치한다.
$ .\install.ps1 # 윈도우여서, / 가 아니고, \이다.

일단 zsh를 사용하기로 한다.

1
$ source ~/.zshrc

폰트 설치 전 셸은 아래와 같이 폰트가 깨짐을 확인할 수 있다.

폰트 설치 및 터미널에서 폰트 설정을 진행하면

아래와 같이 폰트 깨짐이 제거된다.


3. zshrc에 bashrc 설정 옮겨타기

zsh를 사용한 순간 bash에서 제공하는 명령 및 bashrc 파일의 내용이 적용되지 못한다. 따라서 수동으로 복붙해준다.

zsh 적용 후 source ~/.bashrc를 호출해봤더니 셸이 아예 깨져버렸다.


4. zsh 주요 플러그인 2개 설치하기

zsh에서 유명한 플러그인들이 있다. 이러한 플러그인 설치 과정에서 oh-my-zsh의 편리함을 느낄 수 있다. 우리가 설치할 플러그인은 아래 2개이다.


4-1. autosuggestions부터 설치한다.
1
2
3
4
5
6
7
8
9
$ git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions

$ nano ~/.zshrc

# 아래와 같이 수정해준다. (기본값으론 목록에 git만 있을 것이다.)
plugins=(
# other plugins...
zsh-autosuggestions
)

설치된 모습을 확인하면

위와 같이 자동 완성이 기능한다. 오른쪽 방향키를 누르면 끝까지 완성된다.


4-2. syntax highlighting도 설치한다.

아래 명령어로 설치한다.

1
2
3
4
5
6
7
8
9
$ git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting

$ nano ~/.zshrc

# 아래와 같이 수정해준다. (기본값으론 목록에 git만 있을 것이다.)
plugins=(
# other plugins...
zsh-syntax-highlighting
)

아래의 finfind 예제의 차이를 확인할 수 있다. fin만 입력한 경우 찾을 수 없는 명령이어서 미리 실패함을 확인할 수 있고, find의 경우 유효한 명령이어서 사용자가 명령이 성공할 것을 예상할 수 있다.

screen: 리눅스에서 셸을 유지하면서 나갔다 들어오는 방법

이번 글은 Linux 내장 유틸리티인 screen에 대해 소개한다. screen은 셸을 나가도 history가 유지해주는 프로그램이다. tmux와 비교되는 경우도 있는데 화면 분할 및 창 이동이 screen의 주요 기능 중 하나지만 그것만 있다면 대부분 tmux를 사용할 것이다.

아직 screen의 작동 원리는 잘 모르겠지만, 비유적으로 설명하자면 tmux는 셸 단위로 실행되는 것에 비해 screen은 셸 밖에서 실행된다고 볼 수 있다. 따라서 셸을 종료하더라도 screen으로 실행한 셸은 계속 살아있으며, 다른 셸에서 해당 screen 세션으로 접속하면 애초에 접속을 끊지 않은 것 같은 UX를 경험할 수 있는 것이다.

기준 환경:

WSL v2


1. 설치 및 명령어 소개

screen은 Debian 계열에서 기본 설치돼있으므로 설치 방법은 생략한다. (apt-get install screen)

screen은 sudo 권한이 필요하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# screen 세션의 목록을 표시한다.
$ sudo screen -list

# screen 세션의 도움말을 표시한다.
$ sudo screen -h

# screen 세션을 하나 시작한다.
$ sudo screen

# screen 세션에서 빠져나온다.
$ ctrl+a+d

# screen 세션에 다시 접속한다.
# 이름을 짓지 않았던 경우 -list 옵션으로 조회한 후 해당 이름을 넘긴다.
# 이름이 보통 긴데, 도커 파일처럼 unique한 길이까지만 앞 글자만 입력해도 되는 듯하다.
# 정확한 name 쿼리 방식은 잘 모르겠다. `3225.test`인데, 3225도 동작하고 test도 동작한다...
$ screen -r <name>

# screen 세션을 이름을 제공해 하나 시작한다.
# 이 경우 조금 더 접속이 쉬울 것이다.
$ sudo screen -s <name>

# screen 세션을 제거한다.
$ screen -X -S <sessionId> quit

2. 예제

sudo screen -list 명령으로 현재 열려 있는 screen 세션을 확인한다.

현재 3806317 세션이 열려있는 것을 확인할 수 있다. (3806의 경우 이 예제에서 사용하지 않는다.)


sudo screen -r 317 명령으로 317 세션으로 접속하고 임의의 명령들을 실행했다.


이후 ctrl+a+d를 입력해 현재 screen 세션에서 원래 셸로 돌아왔다.


아예 다른 셸에서 sudo screen -r 317로 재접속하였다. 성공적으로 재접속되었고 마치 셸을 닫지 않은 것처럼 그대로 작업이 수행되고 그 기록까지 곧장 확인할 수 있다.


ctrl+a+d로 세션에서 나온 후 sudo screen -X -S 317 quit 명령으로 317 세션을 종료했다.


왜 .profile 파일이 적용되지 않을까

이 글은 ~/.profile 파일이 왜 로드되지 않는지 설명한다.


나는 컨테이너를 활용할 땐 항상 WSL2를 사용한다. 이 때 재밌는 이슈가 있다.

자주 사용하는 퍼블릭 클라우드의 SSH 머신의 IP를 ~/.profile 파일에 export A=B 와 같이 등록해두고 사용하는 등 여러 상수들을 환경 변수로 등록하고 사용했는데, WSL2를 새 터미널로 열면 해당 환경변수가 로딩되지 않았다. 그래서 항상 source ~/.profile을 실행했었는데, 이 글에선 이 이유를 알아보고 그 해결법까지 소개한다.


bash_profile, bash_login, profile 파일

[StackOverFlow]

문제는 생각보다 비직관적이고 간단했는데 ~/.bash_profile 혹은 ~/.bash_login 파일이 존재하면, ~/.profile 파일은 로드되지 않는다고 한다.

나의 경우 bash와 관련된 파일은 .bash_profile, .bashrc가 있었고 추가적으로 .bash_logout, .bash_history 파일이 존재했다.

재밌게도 ~/.bash_profile 파일의 마지막 라인은 source ~/.bashrc 이다. 기본적으로 로딩되는 파일이 아니기 때문이다. (사실 애초에 둘의 차이도 잘 모르겠다.)


Login Shell인지 아는 방법?

[StackOverFlow]

참고로 WSL2는 직접 로그인하는 과정이 생략되지만 Login Shell이다.

shopt -q login_shell && echo 'Login shell' || echo 'Not login shell'를 실행해보면 알 수 있다.


Login Shell의 설정 파일 읽기 루틴

Bash Startup Files

When a “login shell” starts up, it reads the file /etc/profile and then ~/.bash_profile or ~/.bash_login or ~/.profile (whichever one exists - it only reads ONE of these, checking for them in the order mentioned).

When a “non-login shell” starts up, it reads the file /etc/bashrc and then the file ~/.bashrc.

Note that when bash is invoked with the name sh, it tries to mimic the startup sequence of the Bourne shell (sh). In particular, a non-login shell invoked as sh does not read any dot files by default. See the bash man page for details.

충격적이게도, 가장 기본 옵션인 ~/.profile이 무시될 수도 있었다. (대신 모든 사용자를 대상으로 하는 /etc/profile 파일은 무시되지 않고 항상 가장 처음으로 로딩된다.)

현재 Bash가 나의 로그인 셸이고 해당 셸의 설정파일인 ~/.bash_profile이 존재하기 때문에 해당 파일을 로딩한다는 것이다. (~/.bash_profile > ~/.bash_login, ~/.profile 순으로 찾으며, 하나라도 찾으면 해당 파일만 로딩된다고 한다.)


솔루션

솔루션은 크게 두 가지가 있다.

아래 솔루션은 bash를 사용하는 경우로 한정되지만 애초에 bash를 사용하지 않았다면 .profile이 로딩됐을 것이므로 조건에서 생략한다.

  1. bash_profile 혹은 bashrc 파일만 사용하기

  2. bash_profile에 아래 내용 추가하기

    1
    2
    3
    4
    # Load user profile file
    if [ -f ~/.profile ]; then
    . ~/.profile
    fi

추가 참고자료

[StackOverFlow]

[StackOverFlow]

데비안 리눅스 패키지 매니저 dpkg와 apt

이 글은 리눅스의 패키지 매니저와 그 사용 방법을 설명한다.


얘기하기 전에 리눅스 배포판 얘기가 나오면 항상 그 종류가 너무 많아서 이번에 정리를 하려고 한다.

두 가지 주요 리눅스 배포판 - Debian, Redhat

  • 모든 리눅스 배포판의 목록은 여기서 얻을 수 있다.

  • 리눅스 배포판 간의 부모-자식 트리를 보면, 개수 기준으로(점유율 X) 대충 눈대중으로 계산했을 때

    • Redhat 1/4
      • Redhat은 아래의 배포판들을 제작한다.
        • RHEL(GPL 기반 enterprise. 안정성 중점)
        • CentOS(GPL + RHEL의 상표 제거 버전 - 커뮤니티 드리븐인데 레드헷에서 인수했다),
        • Fedora(RHEL의 upstream 버전. 신규 기능 출시 중점)
    • Slackware 1/10
    • 기타 전체 1/4

    정도이므로, 나머지 Debian 기반의 리눅스 배포판의 개수는 전체 배포판 중 40%를 차지한다.

  • OpenSUSE도 여러 글에서 빠지지 않고 Wikipedia에서도 well-known으로 소개되는데, 기반 배포판이 없고, 파생 배포판도 유명한 게 없다.


점유율도 궁금해져서 찾아봤는데,

  • Ubuntu 34%
  • Debian 15% (Ubuntu가 아닌 Debian 및 자식 배포판인 듯하다)
  • CentOS 10%
  • Redhat 1% (RHEL)
  • 알 수 없음 39%

[출처]

으로 Ubuntu가 강세였다. 다만 크고 보수적인 기업일수록 CentOS 혹은 RHEL을 쓰는 게 타당할 듯하다. Ubuntu가 좋지 못하다는 게 아니라, 아무래도 상용 OS 이니까 더 나은 면이 있을 수 밖에 없다고 생각한다.


Debian, Ubuntu의 패키지 매니저 dpkg, apt

dpkg

일단 dpkg는 데비안 배포판에 포함된 패키지 매니저로, 우분투 등의 데비안 기반 배포판에 모두 포함돼있다. dpkg는 설치, 제거, 설치된 패키지 조회가 가능하지만 기능 면에서 부족하다. dpkg는 설치 파일로 .deb 확장자를 쓰며 사용자들이 직접 dpkg로 패키지를 관리하지는 않는다.

Ubuntu가 아닌 Debian도 apt를 사용한다. 즉 apt 역시 모든 데비안 배포판에 포함된 패키지 매니저이다.

1
2
3
4
5
6
7
8
# 패키지 설치
$ dpkg -i <packageFile>

# 설치된 패키지 조회
$ dpkg -l [options...]

# 패키지 제거
$ dpkg -r <packageName>

apt

apt는 사용자들이 사용하는 패키지 매니저이며, dpkg를 감싼 것으로 더 많은 기능을 제공한다.

apt는 프론트엔드라고 불리는데, apt의 주요 기능은 다음과 같다:

  • Search for new packages.
  • Upgrade packages.
  • Install or remove packages (dpkg와 동일).
  • Upgrade the whole system to a new release (= OS 버전 업그레이드).

apt로 묶이는 도구들은 다음과 같다:

  • apt-get for retrieval of packages and information about them from authenticated sources and for installation, upgrade and removal of packages together with their dependencies
  • apt-cache for querying available information about installed as well as available packages
  • apt-cdrom to use removable media as a source for packages
  • apt-config as an interface to the configuration settings
  • apt-key as an interface to manage authentication keys
  • apt-extracttemplates to be used by debconf to prompt for configuration questions before installation
  • apt-ftparchive creates Packages and other index files needed to publish an archive of deb packages
  • apt-sortpkgs is a Packages/Sources file normalizer
  • apt is a high-level command-line interface for better interactive usage

여기서 apt만 사용할 줄 알면 충분하다.


우리가 흔하게 사용하는 apt 명령어를 돌아보자.

1
2
3
4
# 쉽고 직관적인 명령어
$ apt install <packageName>

$ apt remove <packageName>

이 중 평소 따라치라고 해서 따라쳤지만 정확히 무슨 의미인지 몰랐던 명령어들을 정리한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 패키지 목록을 갱신한다. 
# /var/lib/apt/lists 밑에, 레포지토리 이름에 대응되는 파일에 각 패키지와 메타데이터가 저장된다.
# 레포지토리 목록은 /etc/apt/sources.list 파일에 저장된다.
$ apt update

# 가능한 최신 버전으로 업그레이드 가능한 모든 패키지를 업그레이드한다.
# upgrade 명령이 새 버전의 새 의존성에 포함된 패키지를 설치할 순 있어도, 더 이상 필요 없는 기존 패키지를 제거하는 일은 없다.
$ apt upgrade

# upgrade + 현재 버전의 패키지가 삭제돼야 하는 경우 삭제를 수행한다.
$ apt full-upgrade

# apt full-upgrade와 문서화가 유사하게 돼있다. (정확히 무슨 기능인지는 잘 모르겠다.)
$ apt-get dist-upgrade

어떤 패키지의 최신 버전을 설치하려면 설치 전에 apt update를 하는 것은 중요하다.

다만 apt upgrade 등의 작업은 모든 패키지 대상이므로 대개 예상치 못한 결과를 가져올 수 있을 듯하다.


아래는 기타 apt 명령어를 정리한 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# purge는 remove와 다르게, '설정' 파일도 제거한다.
$ apt purge <packageName>

# 미사용 패키지를 추려 모두 제거한다.
# 어떤 패키지가 삭제될지 미리보기는 없는 듯하다.
# 그냥 해봤는데 아무 패키지도 제거되지 않았다.
$ apt autoremove

# 패키지 키워드 검색
# 별로 쓸모는 없는 듯하다.
$ apt search <keyword>

# 설치된 패키지 조회
# 매우 길기 때문에 grep 등으로 조회하는 게 좋겠다.
$ apt list [--upgradable] # upgradable 옵션 사용 시 업그레이드 가능한 패키지 및 업그레이드 버전을 표시한다.

uptime, top으로 CPU 부하 확인하기

이 글은 리눅스 상에서 CPU 자원의 현재 상황을 모니터링하는 방법을 소개한다.

“데브옵스: 개발자, QA, 관리자가 함께 보는 리눅스 서버 트러블슈팅 기법”을 참고했다.

코드가 예전보다 훨씬 느리게 실행돼 응답이 느려졌다면 무엇부터 확인해야 할까?

시스템의 특정 자원을 모두 소비했기 때문에 시스템이 느려질 수도 있다.

여기서 말하는 자원의 종류로는

  • CPU
  • Main Memory
  • Disk I/O
  • Network I/O

이 있고, 이 글은 CPU 부하에 한정해 소개한다.


uptime

load average 다음의 세 실수값이 중요하다.

1
2
$ uptime
05:23:41 up 5 days, 1:41, 0 users, load average: 0.00, 0.00, 0.00

System load averages is the average number of processes that are either in a runnable or uninterruptable state. A process in a runnable state is either using the CPU or waiting to use the CPU. A process in uninterruptable state is waiting for some I/O access, eg waiting for disk. The averages are taken over the three time intervals. Load averages are not normalized for the number of CPUs in a system, so a load average of 1 means a single CPU system is loaded all the time while on a 4 CPU system it means it was idle 75% of the time.

[man uptime]

load average는 실행 중 혹은 인터럽트 불가능한 상태의 프로세스의 평균 개수이다.

  • 실행 중인 프로세스: CPU를 사용 중이거나 CPU 사용을 기다리는 중인 프로세스
  • 인터럽트 불가능한 프로세스: I/O 접근을 대기하는 프로세스

예를 들어 average가 1.00이면 항상 1개의 프로세스가 실행 된다는 것으로 1개의 코어의 로드가 100%라는 의미이다.

[superuser]

멀티코어 CPU에서는 코어 별로 독립적인(병렬적인, parallel) 프로세스(스레드) 실행이 가능하기 때문에 프로세스 개수가 n이면 n이 전체 코어의 로드가 100%임을 나타내는 것이다.

Hyperthreading이 적용되어 CPU에서 2배의 스레드 실행이 가능한 경우 2n이 100%의 로드를 나타낼 것이다.

다시 돌아와서, uptime이 표시하는 세 개의 숫자는 각각 최근 1분, 5분, 15분의 프로세스 평균 개수를 의미한다.

최근 1분의 부하는 현재 시스템에 가해지는 부하를 나타내는 것이다.

아래의 예시를 살펴보자.

1
2
$ uptime
05:23:41 up 25 days, 1:41, 2 users, load average: 17.29, 1.12, 0.10

최근 15분간의 로드가 거의 없던 것에 비해 현재 1분의 로드가 꽤 큰 것을 확인할 수 있다.

  • 따라서 부하는 최근에 시작됐음을 확인할 수 있다.

top

top은 기본값으로 CPU 사용률로 프로세스 정보를 나열하는 유틸이지만, 상단에 표시된 %Cpu(s)행은 CPU 정보를 표시한다.

Name Full Name Description
us User CPU time 사용자 프로세스가 소비한 CPU 사용량 비율 (nice가 적용되지 않은)
sy System CPU time 커널과 커널 프로세스의 CPU 사용량 비율
ni nice CPU time nice가 적용된 프로세스의 CPU 사용량 비율
id CPU idle time CPU가 사용되지 않는 유휴 비율
wa I/O wait CPU가 I/O를 대기하면서 소비한 시간의 비율
hi hardware interrupts 하드웨어 인터럽트를 제공하는데 CPU가 소비한 시간의 비율
si software interrupts 소프트웨어 인터럽트를 제공하는데 CPU가 소비한 시간의 비율
st steal time (비교적 세부 사항이라 생략)

위 표에서 가장 중요한 값은 wa라고 한다. 이 값이 낮으면 I/O가 문제가 아니라고 한다.

I/O 대기(wa)와 유휴 시간(id)이 모두 낮으면 사용자 CPU 사용률(us)을 확인하고 어떤 프로세스가 높은 CPU 사용량을 보이는지 확인해야 한다.

I/O 대기는 낮은데 유휴 시간은 높다면 병목은 CPU가 아니다. (당연하다. 유휴시간이 있다는 것 자체가..)

리눅스에서 tmux와 rabbitmqctl을 이용한 Queue 잔여 메시지 모니터링하기

이 글은 RabbitMQ에서 하나의 One Queue, One Producer, Two Consumer로 구성해 메시지를 처리하는 과정을 모니터링하는 방법을 다룬다.


1. tmux 사용 방법

[jsqna]

이 글에서 사용하는 tmux 명령어는 아래의 명령어로 한정된다.

  • Ctrl + b 를 통해 명령어 모드 활성
  • Ctrl + b 후 "를 누르면 새 가로 패널 생성 (%를 누르면 세로 패널 생성)
  • Ctrl + b + 방향키를 누르면 각 패널 전환
  • Ctrl + b + [를 누르면 현재 패널에서 스크롤 활성화 (기본으로 스크롤이 불가능하다.)

2. RabbitMQ 설치

간단하게 Docker로 설치한다.

1
$ docker run -d --rm --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3.9-management

3. RabbitMQCtl 실행 및 모니터링

해당 컨테이너에 bash로 접속하고 watch 명령어로 rabbitmqctl list_queues를 1초마다 반복 실행한다.

해당 명령어는 현재 RabbitMQ 인스턴스에 존재하는 큐와 잔여 메시지를 출력한다.

1
2
3
$ docker exec -it <container-id> /bin/bash

$ watch -n 1 rabbitmqctl list_queues # 1초마다 해당 명령어를 실행한다.

4. tmux로 MQ, Producer, Consumer 총 4개의 Pane으로 구성하기

코드는 [github]를 사용했다. 코드에 대한 설명은 주석을 참고하기 바란다.

RabbitMQ는 기본 동작으로 Multi Consumer에 대해 Round Robin으로 메시지를 전달한다.

아래와 같이 네 개의 Pane으로 화면을 구성하고, 아래와 같이 구성한다.

1
2
3
4
5
# 두 Pane에서는 Consumer를 실행한다.
$ node receive.js

# 한 Pane에서는 Producer를 실행한다.
$ node send.js

아래와 같은 화면을 구성하면 성공이다.

리눅스 - 클라우드 머신 세팅하기 (Naver Cloud)

이 글은 네이버 클라우드에서 Server 인스턴스를 생성한 후 수행해야 할 가장 기본적인 작업인 계정 생성 및 ssh 설정을 다룬다. 이후 Docker 설치까지 진행한다.


1. Server 생성 및 인증키, 비밀번호 확인

Server 생성

그냥 하면 된다. Zone과 사양만 지정한다.

ACG 생성

그냥 하면 된다. 없으면 하나 만든다. 단순한 방화벽 설정이다.

인증키 다운로드

다운로드 하면 된다. 그냥 key다.

[StackExchange]

WSL을 사용한다면 Windows에서 cp로 WSL에 파일을 옮기고 난 후 chmod 400 $FILENAME.pem으로 적절한 권한을 줘서 보호한다.

비밀번호 확인

서버 콘솔 > 서버 관리 및 설정 변경 버튼 > 관리자 비밀번호 확인

열리는 Modal에 인증키를 업로드하면 확인할 수 있다.

포트포워딩 설정하기

모든 포트를 열어주는 게 아니라 SSH 접속 용도로만 공인 아이피를 제공한다. 1024~65535 사이의 포트는 임의로 지정할 수 있다.

  • 보통 편리하게 2222로 하는 편이다.

  • 애초에 포트포워딩을 안 하더라도 ssh 포트는 변경하는 편이다.

  • 포트 설정의 자유를 제공한 것은

    • enterprise에선 요구사항이 다양하기 때문이고,
    • 통계적으로 특정 포트로 몰리지 않기 때문인 듯하다.

서버 접속

ssh root@SERVER_IP -p PORT 로 비밀번호 방식으로 ssh 접속을 할 수 있다.

2. privatekey 기반 ssh 로그인 구성

ssh의 비밀번호 방식을 허용할 경우 Bruteforce 공격의 대상이 된다.

  • 이는 aws instance를 생성한 후 아무런 정보 없이도 공격이 매우 많이 쌓이는 것을 확인할 수 있다.

이는 임의의 매우 긴 rsa keypair를 사용한다면 공격의 비용이 매우 커지므로 효과적으로 줄일 수 있으며, 일반적인 password에 비해 key의 길이가 훨씬 더 기므로 더 안전할 것이다.

RSA 방식으로 생성된 key로 로그인하는 방식이다.

  • 서버에 publickey를 직접 등록한다. (즉, 최초의 rsa 방식의 연결 이전에 미리 서버에 접근할 수 있어야 한다.)

  • publickey 방식으로 ssh 접속한다.

    1. server에서 메시지를 publickey로 암호화한다.
    2. client에서 메시지를 privatekey로 복호화한다.
    3. client에서 메시지를 전송하고, server에서 일치 여부를 확인하고 접근을 허가한다.

[생활코딩]

1
2
3
4
5
6
7
8
9
10
11
12
13
#-----내 PC------
# 클라이언트에서 privatekey, publickey 쌍을 생성한다.
# id_rsa가 privatekey, id_rsa.pub이 publickey이다.
ssh-keygen -t rsa

#-----원격 서버------

# 본인 클라이언트에서 생성한 데이터로 파일을 생성한다.
# 굳이 파일로 만드는 이유는 cat으로 redirect하기 위함
nano id_rsa.pub

# >> 는 append의 의미이다.
cat id_rsa.pub >> ~/.ssh/authorized_keys

[StackExchange]

authorized_keys는 .ssh 밑에 있으므로 유저 한 명의 범위이다.

같은 privatekey로 여러 사용자로 접속하고 싶다면, /home/{USER}/.ssh/authorized_keys에 모두 등록해주어야 한다.

authorized_keys 등록을 하지 않고 passwordlogin을 못하게 설정하면 이후 해당 서버로는 영영 접속할 수 없게 된다.

  • 클라우드 상에서 직접 접속할 수 있는 콘솔을 제공하는 경우는 될지도 모르겠다.

3. sudo 계정 생성

네이버 클라우드로 인스턴스를 프로비저닝한 경우 계정이 따로 생성되지 않으므로 직접 생성해야 한다.

root 계정을 그냥 사용하는 경우의 단점:

  1. 과도한 권한이 있다. (모든 작업이 가능하다.)
  2. 생성한 파일 소유권이 root가 된다. (root 계정의 사용의 악순환 유발)
  3. 이름이 root이다. (공격 대상이 되기 매우 쉽다.)

계정 생성

[jhnyang]

아래 파일을 생성 즉시 곧장 실행한다면 Permission denied이 발생한다.

chmod +x ./{filename}을 수행하여, 실행 권한을 부여한 후 실행(./{filename})한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/bin/bash
NAME="username"
PWD="password" # 8글자까지면 상관 없음. 길면 truncate 됨.
SHELL="/bin/bash" # 배쉬 사용
ENC_PWD="$(openssl passwd -crypt $PWD)"

echo $ENC_PWD

# 보통 그룹을 이름과 같게 생성해줌
groupadd $NAME

# 사용자 추가
# 비밀번호 지정
# sudo 사용할 수 있게 수정
# 홈 디렉토리 자동 생성
useradd -p $ENC_PWD -s $SHELL -g $NAME -G sudo -m $NAME
  • 신기한 점은, home_directory를 자동으로 생성하면, /etc/skel에 있는 파일이 자동으로 복사되지 않아 ll과 같은 alias를 사용할 수 없다는 점이다. 이는 useradd에서 -m 옵션을 주면 자동으로 생성되며 파일들도 잘 복사되어 해결된다.

  • -p 옵션으로 plaintext로 비밀번호를 전달하니 해당 비밀번호로는 로그인할 수 없었다. 암호화를 적용하니 잘 됐는데, passwd로 변경 시 자동으로 암호화되는 듯하다.

계정 생성, 삭제 과정의 명령어들 모음

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 그룹 조회
cat /etc/group

# 그룹 생성
groupadd $GROUPNAME

# 그룹 삭제
groupdel $GROUPNAME (-f)

# 유저 조회
cat /etc/passwd

# 유저 생성
useradd $USERNAME

# 유저 삭제
userdel (-r) $USERNAME # -r 로 홈 디렉토리까지 제거

sudoer 등록

나중에 수동으로 sudoer에 등록하려면 간단하다.

sudo 그룹에 사용자명을 추가하면 된다.

1
2
3
nano /etc/group

sudo:x:27:user1,user2 # 여기 뒤에 사용자 명을 추가하면 된다.

기본 셸을 bash 사용하게 등록

나중에 수동으로 bash 셸을 사용하게 하려면 간단하다.

1
2
3
4
5
6
7
nano /etc/passwd

# 맨 밑 라인으로 가서,
sb:x:1001:1001::/home/sb:/bin/sh
# 에서
sb:x:1001:1001::/home/sb:/bin/bash
# 로 변경한다.

4. sshd 보안 설정

root 계정 로그인 시도 차단

1
2
3
nano /etc/ssh/sshd_config

PermitRootLogin yes # 이걸 no로 바꾸면 된다.

비밀번호 접속 차단

비밀번호 접속을 차단하지 않으면 bruteforce 공격이 매우 많이 들어오게 된다.

1
2
#PasswordAuthentication yes
# 위 주석을 제거하고 no로 변경한다.

5. Docker 및 Docker-compose 설치

[Docker Docs] [Docker Docs]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash
# Engine 설치
# -y가 필요한 진 모르겠음.
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh

# compose 설치 (Optional)
# 최신 버전의 도커 엔진은 compose 명령세트가 포함돼서 배포되므로
# docker compose 형식으로 사용하면 되므로 굳이 설치할 필요는 없다.

# 참고로 apt install로 깔면 무슨 옛날 버전 나온다.
sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

sudo chmod +x /usr/local/bin/docker-compose

리눅스에서의 파일 디스크립터와 리다이렉션

이 글은 리눅스에서의 파일 디스크립터와 이를 활용한 리다이렉션에 대해 소개한다.


1. 리눅스 표준 스트림 3가지와 파일 디스크립터

각 유닉스 프로세스는 아래 세 개의 표준 스트림에 대응되는 세 개의 POSIX 표준 파일 디스크립터를 갖는다.

파일 디스크립터란 C int 타입의 값이며 아래에서 소개할 파일 디스크립터 테이블의 index 번호이다.

표준 입력, 출력, 오류 스트림은 각 프로세스의 파일 디스크립터의 0, 1, 2번을 할당받는다.

Integer value Name symbolic constant file stream
0 Standard input STDIN_FILENO stdin
1 Standard output STDOUT_FILENO stdout
2 Standard error STDERR_FILENO stderr

리눅스 셸에서의 Redirect는 기본적으로 파일에 쓰고, 파일에서 읽지만 파일 대신 파일 디스크립터를 사용하고, 파일 디스크립터 앞에 &를 붙이면 그대로 적용된다.

예를 들어, command > 1 은 표준 출력으로 Redirect 하라는 의미가 아니라 파일 1로 출력하라는 것으로 해석되며, command >& 1는 파일 디스크립터 1이 가리키는 파일로 출력하라는 것으로 해석되며, 이는 표준 출력이므로, 정상적으로 출력됨을 확인할 수 있다.

참고로 command 2 >& 1의 경우 표준 오류 스트림이 표준 출력 스트림으로 Redirect되는데 왜 2 앞에는 &가 붙지 않아도 될까? 그 이유는…

& is only interpreted to mean “file descriptor” in the context of redirections

라고 한다. (나도 잘 모르겠다. 왜 2>가 stderr를 Redirect 한다는 의미로 쓰일 수 있는지도 모르겠다. 이건 셸 문서를 봐야할듯.)

참고: StackOverFlow


2. 파일 디스크립터 테이블

Process마다 파일 디스크립터 테이블이 하나씩 존재한다. 파일 디스크립터는 index이므로 auto_increment와 같이 1씩 증가하며 발급된다. (따라서 Process에서 처음 파일 디스크립터를 발급 받는 경우 3부터 시작한다.)

아래와 같은 함수들이 파일 디스크립터를 하나 생성한다. (<unistd.h>에 정의돼있다.)

  • open()
  • creat()
  • socket()
  • accept()

네트워크 관련해서도 select(), bind(), listen(), connect() 등 파일 디스크립터와 관련된 함수들이 많은데 아직 C/C++ 기반 TCP/IP 프로그래밍을 해보지 못해서 추후에 다루려고 한다.


3. 파일 디스크립터 조회 및 제한

ls -trn 명령으로 프로세스별 파일 디스크립터를 조회할 수 있다.

ulimit 명령으로 프로세스별 파일 디스크립터 개수의 제한 역시 조회할 수 있다.

이 명령은 또한 그 제한을 바꿀 수도 있는데, ulimit의 open files의 경우 65535까지 가능했다. (이를 초과해서는 root 권한으로도 할 수 없었다.)


환경 변수와 보안

이 글은 리눅스의 환경 변수에 대해 설명하고, 환경 변수가 얼마나 안전한지 다룬다.


1. 환경 변수의 종류

출처: howtolamp, digitalocean

환경 변수란 String Key-Value 쌍으로, 두 종류가 있다. Local Env와 Global Env가 있는데, Local Env는 셸에서 사용되는 변수를 의미하고, Global Env는 흔히 말하는 환경 변수를 의미한다.

2. 환경 변수의 조회와 변경

Local Env는 set 명령으로 조회할 수 있고, 환경 변수는 env 명령으로 조회할 수 있다.

Local Env를 set로 조회하는 경우, 셸 변수, 환경 변수, 로컬 변수, 셸 함수가 출력된다.

Local Env는 {KEY}={VALUE} 형태로 등록할 수 있으며, set | grep {KEY}= 으로 반영됐는지 확인해볼 수도 있다. 단 이런 방식으로 등록한 경우 printenv | grep {KEY}로 확인하면 등록되지 않음을 확인할 수 있다.

printenv는 환경 변수 출력 면에서 env와 같다. 자세한 내용은 stackoverflow 참고

등록된 Local Env는 셸에서 ${KEY}로 참조할 수 있다. 즉, echo ${KEY}로 출력할 수 있다.

Global Env를 등록하려면 export {KEY}={VALUE}를 사용한다.

3. Local Env의 범위

Local Env는 현재 셸 프로세스 및 자식 프로세스 범위에서만 존재한다. 즉, 새로운 터미널 등에서 실행한 셸 프로세스에서 echo ${KEY}를 수행하면 아무것도 나오지 않음을 확인할 수 있다.

4. Global Env의 범위

Global Env의 경우 모든 프로세스에서 접근이 가능하다.

5. Process 별 Env

/proc/{pid}/environ로 프로세스에서 사용되는 환경 변수를 조회할 수 있다. 이 경우 프로세스에 접근할 수 있는 권한이 필요하다.

정확히 무슨 권한이 필요한지는 더 공부가 필요하다.

6. 환경변수의 안전성(secureness)

출처: stackoverflow

환경 변수에 중요 정보를 저장하는 것은 안전하지 않다. 보통 환경 변수가 사용되는 경우는 Credential을 배포할 때인데, 패스워드 등을 저장한 파일과 비교했을 때 더 안전하지는 않다고 한다.

그럼에도 환경 변수를 쓰는 이유가 뭘까?

As mentioned before, both methods do not provide any layer of additional “security” once your system is compromised. I believe that one of the strongest reasons to favor environment variables is version control: I’ve seen way too many database configurations etc. being accidentially stored in the version control system like GIT for every other developer to see (and whoops! it happened to me as well …).

Not storing your passwords in files makes it impossible for them to be stored in the version control system.

즉, Git 등으로 관리하면 실수할 여지가 크기 때문에 환경 변수를 사용한다고 한다.

배포 측면에선 환경 변수가 편리하지만 둘 다 안 좋은 방법이다.

a library author could email stack traces plus the ENV variables to themselves for debugging

즉 라이브러리 등에서도 모두 접근이 가능하기 때문에 (ex: NodeJs의 경우 process.env) 디버깅 혹은 스택 트레이스가 서버로 전송될 수도 있어서, 이런 경우 훨씬 위험할 수 있다.


7. 환경변수는 안전한가?

그렇지 않다.