레이블이 Linux인 게시물을 표시합니다. 모든 게시물 표시
레이블이 Linux인 게시물을 표시합니다. 모든 게시물 표시

yum modular filtering

# rocky 8.8 에 최신 postgresql15 repo 파일을 /etc/yum.repos.d/ 에 생성하고 설치하면
sudo yum install postgresql15-server

# 다음과 같은 에러가 발생한다.
All matches were filtered out by modular filtering for argument: postgresql15-server
# 한글 에러는 요렇다.
일치하는 인수가 없습니다: postgresql15-server

# 기존 postgresql(버전10) 패키지를 비활성화 후 설치하면 된다.
sudo yum module disable -y postgresql

install mysql8 in rocky8

# rocky8 에서 mysql8.0 설치하기 위해선 다음과 같이 외부 저장소로 부터 설치해야 했다.
# 참고로 프록시가 필요한 경우 sudo 실행시 -E 로 proxy 설정 유지가 필요하다.
(프록시설정) sudo -E yum -y install https://dev.mysql.com/get/mysql80-community-release-el8-9.noarch.rpm

# 혹시나 잘못되면 다시 지우고 설치하자!!!(요거 몰라서 삽질)
sudo yum erase -y mysql80-community-release-el8-9.noarch

# mysql80 저장소가 보인다.
yum repolist enabled | grep mysql.*-community

# 기존 mysql 모듈은 비활성화 처리해야 mysql-community-8.0 패키지를 사용할 수 있다.
sudo yum module reset -y mysql
sudo yum module disable -y mysql

# 패지키 확인, 버전이 8.0으로 보인다.
sudo yum info mysql-community-devel mysql-community-server

# 이제 설치하면 된다.
(프록시설정) sudo -E yum install -y mysql-community-devel mysql-community-server

#####

# ansible 사용시
- block
  - name: "Install MySQL8 remote repository for Rocky 8"
    yum:
      # redirect url 사용시 에러 발생
      #name: "https://dev.mysql.com/get/mysql80-community-release-el8-9.noarch.rpm"
      name: "https://repo.mysql.com/mysql80-community-release-el8-9.noarch.rpm"
      state: installed
      sslverify: false
      disable_gpg_check: true
      update_cache: true
    when: ansible_distribution == "Rocky" and ansible_distribution_major_version == "8"

  - name: "Install MySQL8 devel package for Rocky 8"
    yum:
      name: mysql-community-devel
      state: installed
    when: ansible_distribution == "Rocky" and ansible_distribution_major_version == "8"

  - name: "Install MySQL8 server package for Rocky 8"
    yum:
      name: mysql-community-server
      state: installed
    when: ansible_distribution == "Rocky" and ansible_distribution_major_version == "8"

  become: true
  environment: "{{ my_proxy }}"

termux setup storage

폰(안드로이드폰)에서 downloads 에 파일이 너무 많다.
폴더를 생성해서 정리를 하려는데 폰앱으로는 힘든 작업이다.

# termux 를 사용하면 다음과 같이 쉘 커맨드로 처리할 수 있다.
# 다음 명령을 실행하면 ~/storage 위치에 폰 스토리지가 마운트 된다.
# 참고로 이 명령은 termux 설치 후 1번만 수행하면 된다.
termux-setup-storage

# 이제 일반 다음과 같이 파일을 쉽게 이동할 수 있다.
cd ~/storage/downloads
mkdir -p temp
mv *.mp3 ./temp

nfs client

# 다음과 같이 nfs 마운트 실행하면 에러가 발생했다.
sudo mount -t nfs 10.10.10.123:/nfs/ /home/ysoftman/nfs
... for several filesystems (e.g. nfs, cifs) you might need a /sbin/mount.<type> helper program

# nfs 클라이언트 프로그램을 설치하면 된다.
# ubuntu 의 경우
sudo apt-get install nfs-common

# centos 의 경우
dnf install nfs-utils
# 또는
yum install nfs-utils

runc 사용하기

# runc 는 docker, kubernetes(k8s)등에서 사용하는 OCI(Open Container Initiative) 표준을 준수하는 container 기동(spawning and running) CLI 툴이다.

# golang 설치
wget https://go.dev/dl/go1.17.5.linux-amd64.tar.gz
sudo rm -rf /usr/local/go
sudo tar -C /usr/local/ -zxf go1.17.5.linux-amd64.tar.gz
export PATH=$PATH:/usr/local/go/bin
go version

# runc 빌드 및 설치(최신 golang 버전 필요)
cd runc
make && sudo make install

# 일반유저(vagrant)도 docker 명령을 실행할 수 있게 vagrant 계정을 docker 그룹에 추가
# 한번 exit 하고 다시 로그인해야 한다.
sudo usermod -aG docker vagrant

# busybox 이미지의 파일시스템(파일들)을 tar 로 export
docker export $(docker create busybox) -o busybox_exported.tar

# root 계정으로 전환
sudo -Es

# container 에 압축 풀기
# runc 기동시 rootfs 이름의 디렉토리를 찾는다.
cd ~
mkdir -p ./ysoftman_container/rootfs
tar xvf busybox_exported.tar -C ./ysoftman_container/rootfs

# runc 에서 사용할 명세서 생성
# 기본 명세의 config.json 파일이 생성된다.
# spec -> create a new specification file
cd ysoftman_container
tree -L 2
runc spec
ls config.json

# runc 로 ysoftman1 이름의 컨테이너 기동
runc run ysoftman1

# ysoftman1 컨테이너에서 namespace 확인
ls -ahl /proc/$$/ns

# 호스트 터미널 하나더 열고 root 계정으로 전환
sudo -Es

# 호스트에서 runc 같와 runc 가 실행한 sh 프로세스가 확인
ps -ef | grep -E "runc| sh$"

# 호스트에서 
# runc 네임스페이스 확인
lsns -p $(ps -ef | grep -v grep | grep "runc run ysoftman1" | awk '{print $2}')

# ysoftman1컨테이너(runc)가 실행한 sh pid 로 namespace 상태 확인
lsns -p $(ps --ppid=$(ps -ef | grep -v grep | grep "runc run ysoftman1" | awk '{print $2}') -o pid | grep -v PID)

# 호스트에서 runc 로 기동된 컨테이너 리스트 보기
runc list

# 테스트 결과

# runc help
checkpoint  checkpoint a running container
create      create a container
delete      delete any resources held by the container often used with detached container
events      display container events such as OOM notifications, cpu, memory, and IO usage statistics
exec        execute new process inside the container
kill        kill sends the specified signal (default: SIGTERM) to the container's init process
list        lists containers started by runc with the given root
pause       pause suspends all processes inside the container
ps          ps displays the processes running inside a container
restore     restore a container from a previous checkpoint
resume      resumes all processes that have been previously paused
run         create and run a container
spec        create a new specification file
start       executes the user defined process in a created container
state       output the state of a container
update      update container resource constraints
features    show the enabled features

#####

# 위 golang, runc, busybox 준비되어 있는 상태에서
# rootless container 생성해 보기
# root 계정이라면 일반 계정으로 나오기
exit

# ysoftman_rootless_container 에 압축풀기
cd ~
mkdir -p ./ysoftman_rootless_container/rootfs
tar xvf busybox_exported.tar -C ./ysoftman_rootless_container/rootfs

# rootless 옵션으로 runc 에서 사용할 명세서 생성
cd ysoftman_rootless_container
runc spec --rootless

# /tmp/runc 를 루트로 하는 ysoftman_rootless_container 실행
runc --root /tmp/runc run ysoftman_rootless_container

# 컨터이너에서 user id 와 네임스페이스 확인
id
ls -l /proc/$$/ns

# 호스트에서
# 루트 경로 및 state.json 확인해보기
tree /tmp/runc
cat /tmp/runc/ysoftman_rootless_container/state.json | jq . | head -10

# 호스트에서 runc 같와 runc 가 실행한 sh 프로세스가 확인
ps -ef | grep -E "runc| sh$"

# runc 네임스페이스 확인
lsns -p $(ps -ef | grep -v grep | grep "runc run ysoftman_rootless_container" | awk '{print $2}')

# ysoftman1컨테이너(runc)가 실행한 sh pid 로 namespace 상태 확인
lsns -p $(ps --ppid=$(ps -ef | grep -v grep | grep "runc run ysoftman_rootless_container" | awk '{print $2}') -o pid | grep -v PID)

# user 네이스페이스 id 가 ysoftman_rootless_container 가 호스트와 다르다.
# ysoftman_rootless_container 의 root 로 보이지만 호스트의 root는 아니다.

# 테스트 결과

user namespace 로 kubernetes 와 container 보안 향상

user namespace 로 kubernetes 와 container 보안 향상


!!! Caution 잘못된 번역이나 이해로 틀릴 수 있음!!! ^^;;

[user namespace 란?]
user namespace 는 user IDs, group IDs 를 격리(isolate)한다. 리눅스에서 모든 프로세스는 특정 userid(/etc/passwd에 정의)와 groupid(/etc/group에 정의)로 소유된다. user namespace 는 container 안에서는 host의 userid,groupid 의 일부만 볼 수 있도록 한다.
예를 들어 container 안에서의 root(user id 0)는 사실 호스트의 userid=100000 이다.

[user namespace and capabilities]
container 에서 root 로 보이지만 실제는 user namespace 로 구분되는 사용자다. 이와 관련된 linux kernel 히스토리를 살펴보자.

- linux 2.2 이전에는 실제 root(user id 0)는 1개 였다.
- linux 2.2 이후부터 root 권한이 여러개의 기능 구분되는 capabilities(https://man7.org/linux/man-pages/man7/capabilities.7.html)로 나뉘어졌다. ex) CAP_NET_ADMIN : network 설정권한, CAP_SYS_ADMIN : mount 설정권한
- linux 3.8 (2013년) 에 user namespace 가 소개되고 capabilities 는 더이상 글로벌하게 사용하지 않고 user namespace 의 context 에 의해서 해석된다.

예로 sshfs(fuse 로 ssh filesystem 에 마운트) 프로그램을 실행할때 CAP_SYS_ADMIN capability 가 요구된다. 이건 host 와 container 사이를 격리시키는 목적에 맞지 않게 container 에거 너무 막강한 권한을 부여하게 된다.
(참고로 FUSE:Filesystem in Userspace 로 user 레벨에서 파일시스템을 만들 수 있도록 해준다.)

container 가 새로운 user namespace 없이 기동된 상태에서 sshfs 성공하기 위핸선 CAP_SYS_ADMIN 을 허용할 수 밖에 없어 보안 취약점을 내포하고 있다.(호슽와 같은 name space 를 쓰면 sshfs 프로세스가 CAP_SYS_ADMIN 권한을 가지고 있어 container 안에서 host 로 나쁜짓을 할 수 있음?)

하지만 container 가 새로운 user namespace 로 격리돼 실행된다면 호스트 user namespace 의 CAP_SYS_ADMIN 없이도 container 내에서의 CAP_SYS_ADMIN 를 허용할 수 있다.(host user namespace 가 아닌 container user namespace 의 CAP_SYS_ADMIN 로 container 의 mount namespace 의 filesystem 에 마운트하는것이라 호스트에는 영향이 없다.)
container 내에서의 root 는 호스트의 root 가 아니기 때문에 host 에 영향을 줘서는 안된다.

[user namespace and filesystems]
linux 는 user namespace 에서의 mounting 안전을 고려해 FS_USERNS_MOUNT flag 를 마크해 filesystems 리스트를 유지한다. 초기화되지 않은 user namespace 에 새로운 마운트를 생성하려면 FS_USERNS_MOUNT 타입이어야 한다. linux 저장소에서 다음과 검색해보면 리스트 찾아 볼 수 있다. 
git grep -nw FS_USERNS_MOUNT

초기화 되지 않은 uesr namespace 는 초기에는 profs 와 sysfs 타입의 파일 시스템으로 제한되었는데 지금은 아래와 같은 파일 시스템이 허용된다. 이런 파일 시스템은 권한이 없는 사용자도 안전하게 사용할 수 있기 때문에 허용됐다.

파일 시스템         user namespace 에서 허용됨
procfs,sysfs --> linux 3.8, 2012
tmpfs        --> linux 3.9, 2013
cgroupfs     --> linux 4.6, 2016
FUSE         --> linux 4.18, 2018
overlay      --> linux 5.11, 2020
NFS,ext4,btrfs,etc  --> 허용 안됨

[impact on container security]
user namespace 는 호스트와 container 를 격리하는 보안 레이어다.
호스트의 runc(OCI(Open Container Initiative)를 따른 컨테이너 런타임)가 container 에 의해 overwritten 될 수 있는 취약점이 있었다. 이 취약점은  runc 바이너리는 root 소유이고 container 에 매핑되지 않았는데도, 컨테이너의 프로세스가 /proc/self/exe 를 통해 호스트의 runc 를 참조를 허용한다. 
flatcar container linux 는 호스트의 runc 바이너리를 읽기전용 파일시스템을 두어 완화했다.

[enabling user namespace for FUSE filesystem]
linux 커널은 파일이 변경되었다면 읽거나 쓰기전에 IMA(integrity Measurement Architecture) 서브시스템을 사용해 감지하고 감사(audit)한다.
FUSE 와 같은 파일시스템은 커널이 remeasure, reppraise, reaudit 없이 파일을 서빙할 수 있다. 
memfs FUSE driver 의 패치를 테스트 하기 위해 다음과 같은 시나오리가 있다고 하자.
1. 첫번째 요청에 FUSE 드라이버는 파일의 초기 내용으로 서빙된다.
2. IMA 파일에 대한 측정(measurement)을 제공한다.
3. 두번째 요청에 FUSE 드라이버는 같은 파일의 변경된 내용으로 서빙된다.
4. IMA 내용을 다시 측정하지 않아 내용 변경이 측정되지 않는다.

IMA 에 'force' 옵션을 주어 항상 remeasure, reppraise, reaudit 하도록 하는 패치를 생각해보자.
커널이 아무 변경도 감지하지 못했는데 모든 요청마다 강제로 측정이 수행된다. FUSE 에서 다르게 동작하는 부분이 다른 파일스템들에서 모두 알수는 없어, 이로 인해 'force' 옵션이 잘못된 레이어에 추가될 수 있다. 이 문제를 해결하기 위해 IMA 캐싱 관련 옵션 수행하는 FS_NO_IMA_CACHE(v1,v2,v3,v4) flag 를 제공한다. IMA 는 FS_NO_IMA_CACHE flat 를 체크한 후 'force' 이 있으면 사용한다. 이런 방법으로 IMA 은 다른 파일 시스템들 모두를 알 필요가 없다.
하지만 IMA 'force' 옵션은 모든 문제를 해결하지는 못하고 아직 중요사항들은 검증되지 않았다. IMA 관리자들에 의해 linux 4.17 에는 다음과 같은 flag가 구현되어졌다.
- SB_I_IMA_UNVERIFIABLE_SIGNATURE flag : 중요 검증되지 않았음을 표시하는
- SB_I_UNTRUSTED_MOUNTER flag : 신뢰하지 않는 user namespace 에 마운트 되었을 때

[bring user namespace to kubernetes]
kubernetes(k8s)에서는 user namespace 기능을 사용할 수 없다. 2016년 관련 개선을 논의하기 시작했다.
첫번째 시도로 k8s 는 default 가 아닌 Container Runtime Interface(CRI)를 사용해 kubelet 는 CRI 의 gRPC interface 로 컨테이너와 통신하고 주요 목적으로 user namespace 를 제공한다.

kubelet 이 container runtime 와 통신하는 방법

- kubelet docker api 로 통신 (deprecated)
Kubelet --> docker --> containers

- kubelet CRI의 gRPC 로 통신, CRI shim(CRI protocol 처리)
Kubelet --(CRI)--> CRI shim --> container runtime --> containers

- CRI protocol처리하는 containerd/cri 로 컴파일된 containerd 를 사용
Kubelet --(CRI)--> containerd/cri|containerd --(OCI spec)--> runc --> containers

[CRI changes for user namespaces]
user namespace 지원을 위한 CRI 변경 아이디어

1. kubelet 이 sandbox(infrastructure container) pod 시작을 위해 RunPodSandBox()로 container runtime 에 요청하면 pod 안에 container 들의 namespace 를 공유
2. kubelet 이 sandbox 에 CreateContainer(), pod 의 각 container들은 sandbox 를 참조하고 있음.

이렇게 해서 pod 의 컨테이너들은 use namespace 를 공유한다.

IPC 와 network namespace 도 pod 레벨에서 공유된다. IPC, network 는 pod 의 user namespacce 의 의해 소유되고 이 네임스페이스에서의 capabilities 가 허용된다.
pod 의 각 container 의 mount namespace 도 user namespace 의 의해 소유된다. 
(pod 에 network, ipc, user namespace 를 두면 호스트와는 격리된체 pod 내 container 들은 자유롭게 통신할 수 있다.)

[kubernetes volumes]
container 들이 같은 volume에 접근할때 다음 시나리오를 고려해 보자.

1. container1 에서 NFS 에 파일을 쓴다. 이 파일은 uid=10000(container1 mapping 되있음)
2. container2 에서 NFS 의 파일을 읽는다. container2에 uid=10000이 mapping 되어 있지 않아 uid=65534(nobody)로 보여진다. 

이 문제를 해결하기 위해 다음과 같은 방법을 사용한다.

1. 모든 pod 에 매핑되어 있는 uid 를 사용한다. 컨테이너들간의 격리는 줄어들지만 user namespace 사용하지 않는것 보단 안전하다.
2. 각 pod 에 매핑되어 다른 uid 를 사용하고 실제 파일을 사용할때 마다 uid를 convert 할 수 있는 메커니즘(mechanism)기능을 추가한다. linux 커널에는 shiftfs, fsid mapping, new mout api 와 같은 메커니즘을 제공한다. 하지만 아직까지는 linux upstream 제품에는 없다.

오늘날 volume 사용하지 않는 작업이 많이 있고 이는 user namespace 에 있어서 이익이다. 완벽한 해결책을 찾으려고 k8s 에 구현중인 user namespace 를 막지는 말자.

[conclusion]
user namespace 는 linux 에서 container 들을 더 안전하게 사용할 수 있는 유용한 레이어를 제공한다. 이는 지난 몇몇 취약점들을 완화하는 것으로 입증됐다. volume 제공시 단점으로 어려움을 겪고 있지만 linux kernel 에서 개발중이니 앞으로 많이 개선될것으로 기대한다.
언젠가 kubernetes 가 user namespace 를 지원을 완료하면 kubernetes 는 호스트와 컨테이너들 간에 더욱 안전하게 격리 효과를 볼 것이다. 이는 좀더 많은 권한으로 운영되는 container 들에서의 새로운 상황에도 적용된다. user namespace 없이 뭔가를 한다는것 아주 위험하다.

iptables 와 netfilter 이해


!!! Caution 잘못된 번역이나 이해로 틀릴 수 있음!!! ^^;;

[iptables, netfilter 은 뭘까?]

netfilter 는 네트워킹의 모든 패킷들에 의해 트리거되는 패킷 필터링 hook 이다.
iptables 는 커널의 netfilter(패킷 필터 프레임워크) 인터페이스를 사용해 방화벽 규칙을 구성한다.


[netfilter Hooks]

프로그램에 등록할 수 있는 netfilter hook 에는 아래 5개가 있다.
네트워크 스택을 통과할때 아래 5개의 hook 들을 등록한 커널 모듈에 의해서 트리거 된다.
incoming/outgoing 패킷에 따라 트리거 되거나 되지 않는다.
그리고 이전 지점에선 dropped 되거나 rejected 된다.

NF_IP_PRE_ROUTING: 이 hook 은 네트워크 스택에서 들어오고 나서 바로 incoming 트래픽에 의해 트리거된다. 라우팅 결정되기전에 처리된다.
NF_IP_LOCAL_IN: 이 hook 은 incoming 패킷이 라우팅된 후, 라우팅 목적지가 로컬 시스템인 경우 트리거 된다.
NF_IP_FORWARD: 이 hook 은 incomming 패킷이 라우팅된 후, 라우팅 목적지가 로컬이 아닌 다른 호스트인 경우 트리거 된다.
NF_IP_LOCAL_OUT: 이 hook 은 로컬에서 생성된 outbound(외부로 나가는) 트래픽이 네트워크 스택을 만나면 트리거 된다.
NF_IP_POST_ROUTING: 이 hook 은 outgoing,fowrard 트래픽이 네티워트에 위치하기 직전에 트리거 된다.

커널 모듈은 이 hook 들이 트리거 됐을때 우선순위를 결정하기 위해 priority number 를 제공해야 한다.
이는 멀티 모듈(또는 모듈 하나의 멀티 인스턴스)에서도 각각의 hook 의 우선순위 결정을 의미한다.
각 모듈은 순서대로 호출되고 패킷 어떻게 처리되어야 하는지 나타낸 결정이 netfilter 에 리턴된다.


netfilter hook 그림을 보는게 이해하기에 좋다.



[iptables 테이블과 체인]
iptables 방화벽은 rule(규칙)을 구성하기 위해 테이블을 사용한다.
이 테이블은 만들고자 하는 형태에 따라 구분된다.
예를 들어 network address translation 관련한것이라면, nat 테이블에 놓여진다.
만약 패킷이 목적지까지 허용할지 말지를 결정하기 위해서는 filter 테이블에도 추가된다.

iptables 테이블 내에서 규칙은 chain(체인) 으로 나뉘어져 조직화된다.
테이블들이 일반적인 규칙의 목적에 따라 정의 되는 반면, built-in(빌트인) 체인은 규칙들을 트리거하는 netfilter hook 을 나타낸다.
기본적으로 체인은 언제 evalute(평가)될지 결정한다.

아래는 빌트인 체인 이름으로 netfilter hook 과 연관된 이름을 반영한다.

PREROUTING : NF_IP_PRE_ROUTING hook 에 의해 트리거
INPUT : NF_IP_LOCAL_IN hook 에 의해 트리거
FORWARD : NF_IP_FORWARD hook 에 의해 트리거
OUTPUT : NF_IP_LOCAL_OUT hook 에 의해 트리거
POSTROUTING : NF_IP_POST_ROUTING hook 에 의해 트리거

체인들은 관리자가 패킷의 delivery path 에 규칙이 어디로 평가되어질지를 컨트롤할 수 있도록 허용한다.
때문에 각 멀티 체인을 갖는 테이블은 처리중 여러 지점에서 영향을 받을 수 있다.
왜냐하면 결정의 특정 타입은 오직 네티워크 스택의 특정 지점에서 의미가 있기 때문에 모든 테이블은 각 커널 hook 에 등록된 체인을 갖지 못 할 수 있다.

5개의 netfilter kernel hook 있고 멀티 테이블을 가진 체인들은 각각 hook 에 등록된다.
예를 들어 3개의 테이블이 PREROUTING 을 가지고 있고, 이 체인들이 NF_IP_PRE_ROUTING hook 으로 등록되어 있으면� 이 체인들은 각 테이블의 PREROUTING 체인이 호출될 때의 우선순위를 명시한다.
우선 순위가 가장 높은 PREROUTING 체인내의 각 규칙 들은 다음 PREROUTING 체인으로 이동하기 전에 순차적으로 평가된다.
잠시 각 체인들의 구체적인 순서를 살펴보자.


[사용 가능한 테이블과 각 테이블에서 구현된 체인]

각 chain 에 맞는 다음과 같은 성격의 규칙 테이블이 있다.
THE FILTER TABLE: iptables 에서 가장 많이 사용되는 테이블로, 패킷을 목적지로 보내는것을 허용할지 여부를 판단한다.
방화벽 용어로 'filtering' 패킷 으로 알려져 있다.패킷 필터링에 사용하는 테이블, INTPUT,FORWARD,OUTPUT chain 에서 사용
THE NAT TABLE: 출발지, 목적지 변경시 사용하는 테이블, PREROUTING,POSTROUTING chain 에서 사용
THE MANGLE TABLE: 패킷 변조시 사용하는 테이블�, 5개 chain 모두에서 사용
THE RAW TABLE: 주로 netfilter 에서 추적하지 않도록 NOTRACK 설정하는데 사용하는 테이블, PREROUTING,OUTPUT chain 에서 사용
THE SECURITY TABLE: SELinux 보안 사용시 사용하는 테이블, INPUT,FORWARD,OUTPUT chain 에서 사용


[체인 순회 순서]

방화벽이 전송을 허용한다면 다음 흐름은 각각에 상황에서 traverse(순회�) path 를 나타낸다.

incoming 패킷이 목적기자 로컬인 경우: PREROUTING -> INPUT
incoming 패킷이 목적지가 다른 호스트인 경우: PREROUTING -> FORWARD -> POSTROUTING
로컬에서 생성된 패킷: OUTPUT -> POSTROUTING


[iptables 규칙]

규칙은 특정 테이블안애 특정 체인에 놓인다.
각 체인은 호출될때 패킷은 체인 순서대로 각 규칙에 대해 체크한다.
각 규칙은 matching 와 action 구성 요소를 가진다.

matching(매칭)
매칭은 패킷이 액션(또는 target)과 순서대로 만나야 하는 기준을 명시한다.
매칭 시스템은 매우 유연하고 시스템의 사용가능한 iptables 로 확장될 수 있다.
규칙들은 프로토콜타입,출발/목적지 주소/포트,input/output 인터페이스, 헤더, 연결 상태등의 기준으로 구성된다.
다른 트래픽들과 구분하기 위해 규칙 집합(rule sets)으로 결합될 수 있도 있다

targets
타겟은 패킷인 규칙의 매칭을 만났을때 트리거 되는 액션이다.
타겟은 다음 2개의 카테고리로 나뉜다.
- terminating targets: 체인내 평가를 끝내고 netfilter 제어권을 리턴한다. 리턴된 값에 따라 drop 또는 allow 후 다튼 단계로 처리를 이어간다.
- non-terminating targets: 체이내 평가가를 계속 수행한다.

[사용자 정의 체인으로 점프]

non-terminating target 중 jmp target은 추가 처리를 위해 다른 체인으로 이동하는 액션이다.
규칙들은 빌트인 체인이 놓여지는 방식 처럼 사용자 정의 체인에 놓여질 수 있다.
사용자 정의 체인은 빌트인 체인과는 다르게 규칙으로 부터 jumping 으로만 접근할 수 있다.
(사용자 정의 체인은 netfilter hook 에 등록되어 있지 않다.)

사용자 정의 체인들은 이를 호출하는 체인의 단순한 확장 방식으로 동작한다.
예를 들어 사용자 정의 체인에서 규칙 리스트의 끝에 도달하거나 리턴 대상이 매칭 규칙에 의해 활성화되면 평가는 체인을 호출해 통과한다.
평가는 추가적인 사용자 정의 체인들로 점프할 할 수 있다.


[iptables 과 connection tracking(추적)]

connection tracking 은 iptables 이 ongoing connection 의 context 에 보여진는 패킷들 대해서 결정하도록 허용한다.
connection tracking system 은 stateful 한 오퍼레이션을 수행하는데 필요한 기능을 제공한다.

connection tracking 은 패킷이 네트워크 스택에 들어온 후 바로 적용된다.
raw table 체인과 기본적인 체크들은 패킷과 하나의 connection 을 연결하기 전에 패킷에 수행되는 유일한 로직이다.

시스템은 각 패킷들을 존재하는 connection 들 집합과 체크한다.
만약 새로운 connections 이 필요하다면 connection의 상태를 업데이트한다.
raw 체인의 NOTRACK 으로 마크된 패킷들은 connection tracking 라우팅으로 바이패스된다.

시스템은 각각의 패킷들을 현재 존재하는 connection들의 집합과 체크하고, 그것은 connection의 state를 업데이트 한다. 필요할 때 시스템에 새로운 connection을 추가한다. 패킷들은 하나의 raw 체인에서  NOTRACK tarket으로 표시된다. 그리고 connection tracking 경로를 bypass 한다.

Available States
connections tracking 시스템에 의해 connection tracked 는 다음 상태중 하나가 된다.

NEW: 존재하는 connection 연결되지 않는 패킷이 도착할때, 유효하지 않는 첫번째 패킷일때, 새로운 connection 은 NEW label이 추가된다. TCP(커넥션인지),UDP(비연결적인) 프로토콜 모두에서 발생한다.

ESTABLISHED: 유효한 응답을 받았을때 NET->ESTABLISHED 로 변한다. TCP 에서는 SYN/ACK, UDP와ICMP 에서는 트랙픽으로 원래 패킷의 출발지와 목적지가 바뀌는 응답을 의미한다.

RELATED: 패킷이 존재하는 connection 의 일부가 아니지만 시스팀에서 이미 connection 에 연결되어 있따면 RELATED label 이 된다. 이 상태는 helper connection 으로 FTP 데이터 전송 connection 이나 ICMP 응답 connections 시도하는 케이스 이다.

INVALID: 존재하는 connection 과 연결되지 않고 새 connection 을 open 하기에도 적절하지 않은 경우, indentified 될 수도 없고 다른 이유로 라우팅 되지 않는 경우, INVALID 로 마크된다.

UNTRACKED: raw table chain 에 트래킹(추적)을 바이패스하는 타겟으로 된경우 UNTRACKED로 마크된다.

SNAT: 출발 주소가 NAT 연산에 의해서 변경되면 가상 상태가 설정된다. connection tracking system 에 의해 사용되어지며, 출발지 주소가 응답 패킷에서 변경되는지를 알 수 있다.

DNAT: 목적지 주소가 NAT 연산에 의해 변경되면 가상 상태가 설정된다.  tracking system 에 의해 사용되어지며, 목적지 주소가 응답 패킷에서 변경되는지를 알 수 있다.

connection tracking system 은 관리자가 connection의 lifetime 에 있어 특정 시점을 타겟으로 하는 규칙을 만들 수 있도록 허용하고 state 들을 추적한다.


[결론]
netfilter 패킷 필터링 프레임워크가 와 iptables 방화벽은 리눅스 서버에서 대부분의 방화벽 솔루션의 기본이다.
netfilter kernel hook 은 시스템에서 처리되는 패킷들의 강력한 제어를 제공하는 네트워킹 스택에 가깝다.
iptables 방화벽은 유연하고 확장가능한 통신 정책 요구사항 방법을 커널에 제공하는 netfilter 기능을 기반으로 한다.
이 글을 같이 보면서 여러분은 netfilter와 iptables을 보다 잘 다룰 수 있어 여러분의 시스템을 보다 안전하게 할 수 있을 겁니다.

linux namespace 사용하기

# 2002년 리눅스 커널에 포함된 namespace(mnt, pid, net, ipc, cgroup...타입) 로 isolation 구현
# 모든 프로세스들은 특정 네임스페이스에 속하고 unshare 로 새로운 네임스페이스에서 프로세스를 실행할 수 있다. 네임스페이스는 exit 로 빠져나올 수 있다.
# unshare 시 프로세스를 명시하지 않으면 ${SHELL}값(/bin/sh 또는 /bin/bash등)이 디폴트로 실행된다.


#####


# mnt namespace
# 부모(호스트)로 부터 격리된 mount타입(-m, --mount)의 네임스페이스로 /bin/bash 프로그램 실행� 
unshare --mount /bin/sh

# mount_ns 에 마운트하면 현재 네임스페이스에서만 마운트된것을 알 수 있다.
# tmpfs : 가상 메모리 파일시스템
# ushare 된 마운트 스페이스(격리됨)에서 생성한 마운트는 호스트에서 보이지 않는다.
mkdir /tmp/mount_ns
mount -n -t tmpfs tmpfs /tmp/mount_ns
mount | grep mount_ns

# 터미널을 하나 더 띄워 호스트 네임스페이스에서 보면 값이 다른것을 알 수 있다.
readlink /proc/$$/ns/mnt

# 호스트에서 tmpfs 마운트 정보가 안보이는것을 확인
mount | grep mount_ns

# 테스트 결과


#####


# pid namespace
# 새로운 pid(-p, --pid), -f(--fork) 네임스페이스으로 실행
# --mount-proc Just before running the program, mount the proc filesystem at mountpoint (default is /proc)
# --mount-prod 옵션을 사용하면 pid 네임스페이스가 /proc/{pid} 로 마운트되어 쉽게 pid 네임스페이스 접근할 수 있도록 해준다.(mount namspace 도 생긴다.)
unshare -fp --mount-proc /bin/sh

# 호스트 네임스페이스와 현재 pid 네임스페이스에 다른 pid 로 보인다.
# 현재 pid 네임스페이스에서 프로세스를 확인해보면 1번이다.
echo $$
ps -ef

# pid 네임스페이스에서의 pid namespace 정보를 보면
# 아래 호스트의 unshare mount namespace 와 다른것을 알 수 있다.
# unshare(mt ns id:4026532202) ---> pid-namespace /bin/sh (pid ns id:4026532203)
lsns -t pid -p 1

# 호스트에서 unshare 프로세스 pid 찾기
ps -ef | grep unshare

# ushare 프로세스의 namespace 정보
# ls -ahl /proc/{pid}/ns 로도 알 수 있다.
lsns -p {pid}

# mnt 네임스페이스ID 로 확인
lsns {namespace id}

# 테스트 결과 화면


#####


# cgroup namespace
# cgroup 는 controlgroup으로 프로세스의 리소스 제어하는 파일시스템이다.(sys/fs/cgroup)
# sleep 백그라운드 프로세스를 freezer(cgroup 작업 중지,재개를 위한 subsystem)의 sub2로 등록
mkdir -p /sys/fs/cgroup/freezer/sub2
sleep 100000 &
[1] 2932

# 테스트를 위해 sleep pid 환경변수로 설정 
export sleep_pid=2932

# sleep pid 를 sub2 cgroup 으로 등록
echo ${sleep_pid} > /sys/fs/cgroup/freezer/sub2/cgroup.procs

# 현재 프로세스도 freezer sub1 로 등록하자.
mkdir -p /sys/fs/cgroup/freezer/sub1

# 현재프로세스 확인, 테스트를 위해 현재 pid 환경변수로 설정 
echo $$
2178
export parent_pid=$(echo $$)

# 현재 프로세스 pid 를 sub1 cgroup 으로 등록
echo ${parent_pid} > /sys/fs/cgroup/freezer/sub1/cgroup.procs

# freezer (init process, PID 1), parent, sleep pid 확인
cat /proc/1/cgroup | grep freezer
cat /proc/${parent_pid}/cgroup | grep freezer
cat /proc/${sleep_pid}/cgroup | grep freezer

# freezer 파일 확인
tree -L 1 /sys/fs/cgroup/freezer/
/sys/fs/cgroup/freezer/
├── cgroup.clone_children
├── cgroup.procs
├── cgroup.sane_behavior
├── docker
├── notify_on_release
├── release_agent
├── sub1
├── sub2
└── tasks

# 새로운 cgroup(-C, --cgroup) 네임스페이스로 실행
unshare -Cm /bin/sh

# freezer (init process, PID 1), parent, sleep pid 확인해보면
# sub1 --> / 가 루트가 되고
# sub2 --> ../sub2 로 변경되었지만 호스트 cgroup 은 그대로 보인다.
cat /proc/1/cgroup | grep freezer
cat /proc/2178/cgroup | grep freezer
cat /proc/2932/cgroup | grep freezer

# 테스트 결과


#####


# stress 툴로 시스템 부하(cpu)를 테스트해보자.
# new_root 에 bash 준비하기
mkdir -p new_root/{bin,usr,lib,lib64} new_root/lib/x86_64-linux-gnu/ new_root/usr/lib/x86_64-linux-gnu
cp /bin/bash new_root/bin
cp /bin/mount new_root/bin
cp /lib/x86_64-linux-gnu/{libtinfo.so.5,libdl.so.2,libc.so.6} new_root/lib
cp /lib64/ld-linux-x86-64.so.2 new_root/lib64

# mount 명령등 준비하기
cp -rv /bin/{ls,cat,mount,umount,mkdir,rmdir,rm,cp,grep,ps} new_root/bin
cp -rv /usr/lib/x86_64-linux-gnu/* new_root/usr/lib/x86_64-linux-gnu
cp -rv /lib/x86_64-linux-gnu/* new_root/lib/x86_64-linux-gnu
cp -rv /lib64/* new_root/lib64

# stress, cgroup 툴 설치
apt-get install -y stress cgroup-tools

# chroot 후 필요한 파일들 복사
mkdir -p new_root/usr/bin/bin
cp /usr/bin/{top,stress,cgcreate,cgset,cgexec} new_root/usr/bin/

# top 실행시 필요한 파일들 복사
mkdir -p new_root/usr/share/terminfo
cp -r /usr/share/terminfo/ new_root/usr/share/terminfo

# cgcreate 시 root uid 파악에 필요
mkdir -p new_root/etc
cp /etc/group new_root/etc/group
cp /etc/passwd new_root/etc/passwd

# cgroup 관련 마운트 확인
mount | grep -E "cgroup*.cpu"

# chroot 후 mount 하기
chroot new_root
mkdir -p /sys/fs/cgroup
mkdir -p /sys/fs/cgroup/{cpuset,"cpu,cpuacct"}
mount -t cgroup -o cpuset cgroup /sys/fs/cgroup/cpuset
mount -t cgroup -o cpu,cpuacct cgroup /sys/fs/cgroup/cpu,cpuacct

# /proc 마운트
mkdir -p /proc
mount -t proc proc /proc

# mycgroup 생성 및 확인
cgcreate -a root -g cpu:mycgroup
ls -al /sys/fs/cgroup/cpu,cpuacct/ | grep mycgroup

# CPU 사용률(%) = (cpu.cfs_quota_us / cpu.cfs_period_us) * 100
# cpu.cfs_period_us 를 확인해 보면 100000(us, 100ms) 이다.
# cat /sys/fs/cgroup/cpu,cpuacct/cpu.cfs_period_us
# 따라서 cpu.cfs_quota_us=30000 을 주면 30% cpu 사용률로 제한한다.
cgset -r cpu.cfs_quota_us=30000 mycgroup

# mycgroup 에 strecc -c(--cpu) 1로 부하 테스트 백그라운드로 실행
cgexec -g cpu:mycgroup stress -c 1 &

# top 으로 stress 프로세스 30% 사용 확인
export TERM="xterm-256color"
top
  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
 3129 root      20   0    8248     96      0 R  30.0  0.0   0:03.50 stress

# 테스트 결과


#####


# net namespace
# 새로운 network(-n, --net) 네임스페이스으로 실행
unshare --net /bin/sh

# 호스트 네임스페이스와 정보가 다른것을 알 수 있다.
ip a

# 테스트 결과

# ip 로 net namespace 생성해보기
ip netns add mynet

# net namespace 확인
ip netns list
# 또는
ls /var/run/netns

# nsenter 로 net네임스페이스(--net)에 들어가기
# 별도 프로그램 명시하지 않으면 디폴트로 $SHELL -> /bin/bash 를 실행한다.
nsenter --net=/var/run/netns/mynet
ip a

# net 네임스페이스에서 현재 프로세스의 pid,user,ipc,mnt등의 네임스페이스 정보 확인
lsns -p $$

# 호스트에서 net 네임스페이스 id 확인
# readlink /proc/$$/ns/net 대신 lsns 명령어로도 네임스페이스 정보를 확일 수 있다.
# lsns -t 타입 -p pid
lsns -t net -p $$

# 테스트 결과


#####


# user namespace 생성
# 새로운 user(-U, --user) 네임스페이스으로 실행
unshare --user /bin/sh

# 새로운 user 네임스페이스에서의 사용자 정보 확인
whoami
id

# 네임스페이스 확인해보기
ls -al /proc/$$/ns
lsns -p $$

# 테스트 결과 화면

# container 와 호스트 root 가 같은것인지 확인해보기
# docker 로 busybox 컨테이너 띄우기
docker container run -it --name busybox --rm busybox

# ps 로 보면 root 로 실행됐고, uid=0 으로 되어 있다
ps -ef
id

# 현재 프로세스의 user 네임스페이스 확인
readlink /proc/$$/ns/user

# 호스트에서도 확인해보면 root 이름이고 uid=0 이다.
ps -ef | grep root | head -3
id

# 호스트에서 현재 프로세스의 user 네임스페이스 확인해보면 위 container user namespace id 와 같은것을 알 수 있다.
# 결국 container 의 root 도 호스트 user 네임스페이스를 공유하기 때문에 호스트 root 와 같은것이다.
readlink /proc/$$/ns/user

# 테스트 결과 화면

# 참고로 docker v1.10 이상 부터는 별도의 user namespace 를 지원하지만
기본적으로 docker 로 컨테이너를 실행하면 root 로 프로세스를 실행한다.(user namespace 를 별도로 만들지 않고 호스트를 user namespace 를 사용한다.)
# 이 때문에 container root 계정이 악용될 수 있음을 인지해야 한다.
# docker remap 설정으로 uid 를 다르게할 수 있다. 아래 별도 포스트로 정리


#####


# uts(unix time sharing system) namespace
# 새로운 uts(-u, --uts) 네임스페이스으로 실행
unshare --uts /bin/sh

# 호스트명 변경하고 확인
hostname ysoftman
hostname

# 터미널을 하나 더 띄워 호스트를 확인해 보면 영향을 주지 않는것을 알 수 있다.
# 테스트 화면


#####


# ipc(interprocess communication) namespace
# shared memory, queue, semaphore 등의 리소스에서 사용되며 같은 ipc 네임스페이스안 프로세스들은 ipc 통신할 수 있다.
# ipc (shared memory, pipe) 상태 정보 보기
# -m  Write information about active shared memory segments.
ipcs -m

# ipc shared memeory 리소스 1000byte 생성
# -m Create a shared memory segment of size bytes.
ipcmk -M 1000

# 1000 byte shared memory segemnt 가 생성된것을 확인할 수 있다.
ipcs -m

# 새로운 ipc(-i, --ipc) 네임스페이스 생성
unshare --ipc /bin/sh

# 새로운 ipc 네임스페이스에서는 호스트에서 만든 1000bytes 리소스가 보이지 않는다.
ipcs -m

# 호스트로 나오기
exit

# ipc 리소스 삭제
# -M Remove the shared memory segment created with shmkey after the last detach is performed.
ipcrm -M {shared memory segment key}

# 삭제된 리소스 확인 
ipcs -m

# 위 과정을 테스트한 결과

chroot, pvirot_root, unshare

# container 없던 시절 완벽하진 않은 격리효과를 위해서 사용할 수 있는 명령들

# 가상작업환경 : virtualbox + vagrant + ubuntu box image
# vagrant up 후 vagrant ssh 로 접속
# root 환경에서 작업하기
sudo -Es

# 마운트등의 이슈로 /tmp 에서 작업한다.
cd /tmp

# chroot 사용하기
# 새로운 root 경로가 될 디렉토리 생성
mkdir new_root

# 단순히 chroot new_root 하면
chroot new_root
# 다음과 같은 에러가 발생한다.
chroot: failed to run command ‘/bin/bash’: No such file or directory

# chroot 하면 bash 가 실행하는데 new_root 에서는 bin/bash 가 없어 이를 새루트에 복사해야 한다.
# bash 구동을 위해 필요한 파일들 확인하자
ldd /bin/bash
linux-vdso.so.1 (0x00007ffe49bfb000)
libtinfo.so.5 => /lib/x86_64-linux-gnu/libtinfo.so.5 (0x00007fc6e4d28000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fc6e4b24000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fc6e4733000)
/lib64/ld-linux-x86-64.so.2 (0x00007fc6e526c000)

# bash 및 관련 라이브러리 복사
mkdir -p new_root/{bin,usr,lib,lib64} new_root/lib/x86_64-linux-gnu/ new_root/usr/lib/x86_64-linux-gnu
cp /bin/bash new_root/bin
cp /lib/x86_64-linux-gnu/{libtinfo.so.5,libdl.so.2,libc.so.6} new_root/lib
cp /lib64/ld-linux-x86-64.so.2 new_root/lib64

# ls 외 필요한 기본 명령어도 확인하고 복사하자.
# ps 는 /proc 디렉토리를 생성(mkdir)하고 mount 해야 사용할 수 있다.
ldd /bin/ps
linux-vdso.so.1 (0x00007ffc403b6000)
libprocps.so.6 => /lib/x86_64-linux-gnu/libprocps.so.6 (0x00007f594c99d000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f594c799000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f594c3a8000)
libsystemd.so.0 => /lib/x86_64-linux-gnu/libsystemd.so.0 (0x00007f594c124000)
/lib64/ld-linux-x86-64.so.2 (0x00007f594ce03000)
librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007f594bf1c000)
liblzma.so.5 => /lib/x86_64-linux-gnu/liblzma.so.5 (0x00007f594bcf6000)
liblz4.so.1 => /usr/lib/x86_64-linux-gnu/liblz4.so.1 (0x00007f594bada000)
libgcrypt.so.20 => /lib/x86_64-linux-gnu/libgcrypt.so.20 (0x00007f594b7be000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f594b59f000)
libgpg-error.so.0 => /lib/x86_64-linux-gnu/libgpg-error.so.0 (0x00007f594b38a000)

# 일일히 lib 복사하지 말고 그냥 lib,lib64 파일 모두 복사해놓자.
cp -rv /bin/{ls,cat,mount,umount,mkdir,rmdir,rm,cp,grep,ps} new_root/bin
cp -rv /usr/lib/x86_64-linux-gnu/* new_root/usr/lib/x86_64-linux-gnu
cp -rv /lib/x86_64-linux-gnu/* new_root/lib/x86_64-linux-gnu
cp -rv /lib64/* new_root/lib64

# 이제 chroot 를 해보면 /bin/bash 가 실행된다.
chroot new_root
# root 경로가 바뀐것 확인(cat,mkdir,ps 등도 동작한다.)
ls /

# ps 명령을 실행하기 위해선 /proc 디렉토리를 만들고 마운트해야한다.
mkdir -p /proc
mount -t proc proc /proc

# chroot 는 다음코드를 실행하면 현재 root 경로를 나갈수 있다(탈옥가능)
cat > break_chroot.c << EOF
#include <sys/stat.h>
#include <unistd.h>
int main(void)
{
    mkdir("foo", 0755);
    chroot("foo");
    chdir("../../../../../");
    chroot(".");
    return execl("/bin/bash", "-i", NULL);
}
EOF
# 빌드
gcc break_chroot.c -o break_chroot
# new_root 에 복사하고
cp -v break_chroot new_root
# new_root 에서 실행하면 chroot 의 / 를 빠져나가는 이슈가 있다.
chroot new_root
./break_chroot

# chroot 나가기
exit


#####


# pivot_root : root 를 new_root 아래로 mount 하여 경로 탈옥을 막아보자.

# 새로운 root 경로가될 디렉토리 생성
mkdir new_root2

# unshare 는 부모로 부터 격리된 특정 네임스페이스로 프로그램 실행�한다.
# -m : unshare mount namespace
# 프로그램 명시하지 않으면 기본 $SHELL(/bin/sh 등) 를 실행한다.
unshare -m

# 새로운 파일시스템을 생성해 new_root2 에 마운트
# 참고로 
# chroot 테스트에서 사용했던 파일들 모두 사용하기 위해 디스크 크기를 넉넉하게 한다.
# 디스크 크기를 너무 적게 설정하면 mount 오류로 다음과 같은 에러가 발생할 수 있다.
# -bash: /bin/ls: Input/output error
mount -t tmpfs -o size=500M none new_root2

# chroot 테스트에서 사용했던 파일들 복사해오기
cp -rv new_root/* new_root2/

# root 경로를 마운트할 디렉토리 생성
mkdir new_root2/put_old
cd new_root2

# pivot_root: change the root filesystem
# 현재 프로세스의 root 파일시스템을 put_old 로 옮기고 . 를 새로운 root 파일 시스템으로 만든다.
pivot_root . put_old

# root 파일 시스템 자체가 바뀌었음으로
# chroot 탈옥 코드로 root 경로 탈옥이 되지 않는다.
./break_chroot

# unshare 나가기
exit

process 파일 이용하기

# 리눅스는 모든게 파일이다. 프로세스도 /proc/프로세스ID 파일로 확인할 수 있다.
# 현재 프로세스가 실행중이면 커맨드라인 파악
cat /proc/$$/cmdline

# 특정 (httpd) 프로세스의 쉘 환경변수 중 ysoftman 값 파악
sudo cat /proc/$(ps -e -o pid,command | command grep -i httpd | head -n1 | awk '{print $1}')/environ | tr '\0' '\n' | grep -iE ^ysoftman

# 참고로 현재 프로세스(pid)의 stdout 을 보기 위해 
# 새 터미널에서 cat /proc/{pid}/fd/1 를 실행후 입력하면 문자가 개별적으로
# pid 가 실행중인 /dev/pts/1 과 cat 이 실행중인 /dev/pts/2 중 한쪽에만 출력된다.


pid 네임스페이스에 관한 이슈와 해결책

[들어 가기전]
고아(orphan) process : 부모 프로세스가 자식 프로세스보다 먼저 종료되면, 자식 프로세스는 고아 프로세스가 된다. 고아 프로세스는 init(pid 1)에서 거둬 들여, 고아의 부모 프로세스는 init(pid 1)이 된다. 고아 프로세스가 종료되면 init 이 종료 상태를 파악해 좀비 프로세스가 되는것을 막는다.

좀비(zombie/defunct) process : 자식 프로세스는 종료되었지만 부모 프로세스가 wait()등으로 자식 프로세스의 종료 상태를 파악하지 못해 자식 프로세스를 거둬 드릴 수 없는 경우로 process table 에만 남아 있어 kill -9 좀비프로세스PID 로도 삭제되지 않는다.
좀비 프로세스를 삭제 방법
- kill -CHLD PPID (부모 프로세스가 CHLD 시그널을 처리할 수 있는 경우에만 가능)
- kill -KILL PPID (좀비 프로세스의 부모프로세스를 제거한다. 모든 child 가 죽기 때문에 신중해야함)

pid 네임스페이스에 관한 이슈와 해결책을 보고 정리해 봤다

!!! Caution 잘못된 번역이나 이해로 틀릴 수 있음!!! ^^;;


[linux 의 pid(process identifier)]

user space 의 root 프로세스는 init (pid 1, 이것의 부모는 pid 0 으로 커널)이다.
1 번 프로세스가 죽으면 커널이 패닉을 발생해 시스템을 재부팅해야 한다.


[linux namespace 생성]

리눅스 네임스페이스들(uts(host, domain 구분), network, mount, ipc, cgroup(cpu,memory,disk,network 등 리소스 구분), pid)는 unshare 시스템콜로 생성한다. unshare 하면 바로 새로운 네임스페이스로 들어간다.
하지만 pid 네임스페이스의 경우 unshare 로 바로 pid 네임스페이스로 들어가지 않고, fork 로 pid 네임스페이스에 pid 1(init)번를� 만든다. 이 1번 프로세스는 pid 네임스페이스 밖(호스트)에서는 다른 pid 값이다.


[네임스페이스에서의 pid 1]

1. pid 네임스페이스의 init 프로세스는 기본 signal 핸들러가 없어, HUP, INT, QUIT, TERM, KILL .. 등의 시그널 처리를 하지 못한다.(별다른 옵션 없이 docker 컨테이너를 수행하면 ctrl-c 가 동작하지 않는다. 그래서 docker kill 등을 사용한다.)

2. pid 네임스페이스의 init 외 다른 프로세스가 자식 프로세스보다 먼저 죽으면 그 자식 프로세스의 부모는 init (pid 1)이 되고, init 은 프로세스들이 exit 상태를 수집해 커널이 process table 에서 정리할 수 있도록 한다.

3. pid 네임스페이스의 init(pid 1)이 죽으면, 같은 네임스페이스의 모든 프로세스가 강제로 제거되며, pid 네임스페이스는 clean up 된다.


[docker 의 실수]

docker(runc:lightweight docker runtime)는 컨테이너의 entrypoint 를 pid 1 로 하는 새로운 pid 네임스페이스를 만든다. pid 1번에서 signal 처리가 되어 있지 않으면 ctrl-c 와 같은 signal 이 동작하지 않는다. 손자 프로세스가 종료(exit)되기 전에 죽은 자식 프로세스를 fork 하게 되면 zombie(좀비) 프로세스가 컨테이너에 쌓이게 되고, 결국 process table 을 꽉 채우게되는 상황이 발생할 수 있다.
이런 문제를 피하기 위해 fork-exec 를 할 수 있는 특별할 application process 를 실행할 수 있다. 이는 컨테이너가 복잡해지지만 실제 호스트(컨테이너가 아닌)의 init 처럼 만들 수 있다. 사람들은 종속성 격리의 이점을 희생하더라도 여러개의 apt 를 실행(dockerfile 에서 apt install a b c 컨테이너 내에서 패키지를 설치)을 포함시키는 경향이 있다.


[Rkt 해결책]

Rkt(rkt is an application container engine) 로 위 문제를 다소 해결할 수 있다. Rkt 는 사용자가 만든 시작 프로세스(container process)가 init 이 아니라고 가정하고 init (systemd) 를 생성하고 systemd 로 컨테이너에서 동작하는 filesystem 네임스페이스를 만들고 구동시킨다. systemd 는 pid 1번이 되고, (사용자가 만든) container process 는 pid 2번으로 동작한다. 만약 container process 가 init 이라면 이것도 pid 2 로 동작해 또 다른 이슈가 될 수 있다.


[간단한 대체 방법]

fork 후 pid 네임스페이스에서 container process 를 바로 실행하는 대신, spawner 가 다시 fork 한다. 이 두번째 fork 는 container spawner 가 pid 1이 되도록 하고, 모든 child 프로세스들에 signal을 전달할 수 있는 signal handler 를 구성한다. 이렇게 하면 child 프로세스가 죽을때 까지 좀비 프로세스를 거둬들일 수 있고, 이 시점에 container process 의 종료 상태를 수집해 컨테이너화 시스템에 전달한다. 이것은 ctrl-c 와 같은 signal 을 처리할 수 있고 zombie 프로세스도 적절히 거둬들 수 있다는 의미다.
docker 1.13 부터 가능하며 --init 플래그를 사용하면 된다.


[하나의 pod 내에서의 다중 컨테이너]

pod 는 네임스페이스를 공유 하는 컨테이너들의 집합이다. rkt 에서는 파일시스템 네임스페이스를 제외한 모든 네임스페이스들이 공유된다.
앞서 pid 네임스페이스 이슈로 kubernetes(k8s) 는 pod 내 컨테이너들간에 pid 네임스페이스를 공유 하지 않는다. 때문에 같은 pod 내의 container 들 끼리 signal 주고 받을 수 없고, 앞서 언급한 init 문제를 가지고 있어 container processor 는 pid 1로 실행된다.
rkt 방법을 사용하면 컨테이너 안에 init 프로세스를 만들지 않아도 통신이나 signal 을 주고받을 수 있는 컨테이너들을 쉽게 생성할 수 있다. 하지만 이미 존재하는 pod 에 컨테이너를 추가하는 상황에서는 바로 되지는 않는다.


[pod 에 컨테이너 추가하기]

k8s 는 pod sandbox 라는 개념이 있다. k8s 컨테이너 런타임은 컨테이너 시작에 앞서 리소스를 할당하는데, 이는 특히 네트워킹에 유용하고 존재하는 pod 에 컨테이너를 추가하는데도 적용할 수 있다. sandbox pod 를 먼저 생성 후 컨테이너를 하나씩 추가하는 경우, 왜 나중에 컨테이너 추가하는게 허용되지 않을까? 이는 데이터베이스 백업이나 로그 수집과 같은 주기적인 작업에 특히 유용할 것이다.
rkt(에서는 container 를 app 으로 부른다.)는 실험적으로 컨테이너와 독립적으로 pod 생성을 허용하여, 추후에라도 pod 에서 컨테이너를 추가,삭제할 수 있다. 별도 실행되는 유닛(프로세스?)없이 systemd 로 가능하게 한다. 새로운 app(container) 시작 요청이 있으면 pod 의 systemd 와 통신한다. 이러한 모델로 init 프로세스는 다음과 같은 추가적인 특권(privilege)을 가진다.
- 호스트 파일시스템 네임스페이스에 접근할 수 있어서 컨테이너 시작을 위한 파일시스템 네임스페이스를 생성할 수 있다.
- 각 app 시작에 필요한 privilege 들을 알 수 없어 전체 privilege 을 유지해야 한다.
- 컨테이너상의 모든 실행중인 프로세스를 볼 수 있다.
non-sandbox 모델이라면 init 프로세스는 child 프로세스를 시작한 다음 compromise 영향을 최소화 하기 위해 privilege 를 삭제한다.


[sandboxes 와 pid namespaces]

init, sandboxes, pid namespace 를 이용한 방법들 각각 약간의 결점(단점)이 있고, 다음 옵션을 사용할 수 있다.
1. pid namespace 가 sandbox 와 같이 생성되지 않을때, 각 컨테이너는 자신의만의 pid 네임스페이스를 가진다. k8s 에서 사용하는 방식이며, signal 처리를 위한 간단한 init 전략이다. 중요 단점으로는 pod 내 프로세스들간 signal 을 통신을 할 수 없다.
2. pid namespace 가 sandbox 와 같이 생성되지 않을때, sandbox 에서 처음으로 시작되는 컨테이너에 pid namespace 가 생성된다. 이 상태에서는 프로세스간 signal을 주고 받을 수 있다. 단점은 첫번째 프로세스가 pod 의 master 가 되고 이 master 프로세스가 죽으면 컨테이너 내 모든 프로세스는 커널에 의해 종료된다. 때문에 master 프로세스는 pod lifecycle 동안 살아 있어야 한다.
3. pid namespace 가 sandbox와 함께 생성될때, 다른 프로세스를 실행할 수 있는 smart init 프로세스를 포함한다. 이것은 rkt app sandbox 가 동작하는 방식이다. 단점으로는 init 프로세스가 너무 많은 privilege 를 가져 그 만큼 보안 공격에 취약할 수 있다.
4. pid namespace 가 sandbox와 함께 생성될때, sandbox 는 signal을 처리하고 zombie 프로세스를 회수하는 간단한 init 프로세스를 포함한다. 다른 프로세스들은 pid namespace 로 들어갈 수 있지만 부모 프로세스들은 네임스페이스 밖에 있다. init 프로세스는 새로운 컨테이너를 시작하지 않기 때문에, privilege 나 호스트 파일시스템 액세스를 유지할 필요가 없다. 단점은 네임스페이스 내의 각 프로세스들은 pid 0인 부모를 가져 일반적인 process tree 가 깨진다.
5. pid namespace 와 init 이 정확히 위 4번 옵션과 같을때, 다른 프로세스들은 pid namespace 에 들어가고 daemonize 된다. 커널은 위 4번에 pid 0으로 깨진 process tree 에서의 부모를 init 으로 돌릴 것이다. 단점은 daemonize 후 밖에 있는 새로운 프로세스를 모니터링 하기 어렵다. containerizationo system 은 간단하게 프로세스를 waiting 하는 대신 pid 를 통해 프로세스를 추적하도록 강요된다.

(필자는) 위 옵션 중 4번, 5번을 선호한다. 사실 기대하는 프로세스의 lifetime 에 따라 하나를 선택하면 된다. 위 옵션들은 프로세스를 오래 실행하기 위한 좋은 방법들이고 특히 프로세스를 spawner 로 daemonize 프로세스를 종료하는 docker 환경에 좋은 솔루션이다. 만약 간단하게 끝나는 프로세스라면 4번 옵션을 사용하고 프로세스를 pid process tree 와 분리해 유지하면 아주 쉽다. 
어떤 작업들은 k8s 가 init 처럼 동작하는 pause container를 생성하는것과 비슷하다. k8s 에 pid namespace 공유가 지원되면 5번 옵션도 가능할것이다.


[결론]

pid namespace 에는 꽤 숨겨진 복잡도가 존재한다. 오늘날 containerization 시스템은 중요한 단점과 이걸 피할 수 있는 방법들에 따라 선택한다. docker 의 싱글 컨테이너의 단점은 잘 이해되고 적당한 해결방법이 있는 반면, 컨테이너 spawner 가 init 처럼 동작하게 허용되면 컨테이너 빌더 작업이 단순화된다.
그룹 컨테이너의 경우 init 분리 방식이 rkt 가 docker 보다 우수하다. rkt 방식은 현재 k8s pod 모델에서 가능하지 않는 signal 을 통한 프로세스간 통신을 허용한다. 하지만 지연된 시작 컨테이너가 포함되기 때문에 rkt 방식도 몇가지 단점을 보인다.
지연된 시작 컨테이너에 가장 강력한 해결 방법은 pid namespace 와 함께 간단한 init 프로세스를 시작하는것이다. 하지만 새로운 컨테이너를 생성하려면 container spawner 를 통해야 한다. 이는 init 프로세스가 privileges 삭제를 허용하여, 공격을 차단한다. spawner 는 새 프로세스를 daemonize 하고 process tree 를 일관되게 유지하거나 새 프로세스의 부모로 남아 프로세스 관리를 단순화할 수 있다.


linux ip iptables 사용하기

# linux 에서 자주 사용되는 ip 명령 예제들
# 참고로 다음과 같이 각 명령에 대한 도움말을 볼 수 있다.
ip addr help 
ip link help 

# 네트워크 장치�ipv4, ipv6 주소 정보
ip addr

# ip 주소할당, dev(device)로 eth0 를 사용
ip addr add 10.10.10.2/24 dev eth0

# ip 주소 삭제
ip addr add 10.10.10.10/24 dev eth0
ip addr delete 10.10.10.10/24 dev eth0

# 장치별 링크 정보 
ip link

# 라우터 정보
ip route

# 연결된 이웃 장치 정보
ip neighbor


##### 네트워크 네임스페이스 구분해서 통신하기 #####


# veth(virtual Ethernet devices) 타입으로
# lemon_eth0 apple_eth0 를 생성하고 연결한다.
ip link add lemon_eth0 type veth peer name apple_eth0

# namespace 확인
ip netns

# LEMON namespace(네트워크가 호스트 네트워크와 격리됨) 생성
# /var/run/netns/LEMON 파일이 생성된다.
ip netns add LEMON
ip netns add APPLE

# lemon_eth0 의 네임스페이스를 LEMON 으로 설정
ip link set lemon_eth0 netns LEMON
ip link set apple_eth0 netns APPLE

# LEMON 네임스페이스에 접속해서 lemon_eth0 장치 시작하기 
ip netns exec LEMON ip link set lemon_eth0 up
ip netns exec APPLE ip link set apple_eth0 up

# LEMON 네임스페이스에 접속해서 lemon_eth0 장치에 ip 설정하기
ip netns exec LEMON ip addr add 10.10.10.2/24 dev lemon_eth0
ip netns exec APPLE ip addr add 10.10.10.3/24 dev apple_eth0

# APPLE apple_eth0 의 패킷을 모니터링 하고
ip netns exec APPLE tcpdump -l -i apple_eth0
# LEMON(lemon_eth0) --> APPLE(apple_eth0) 로 핑을 보내 확인 할 수 있다.
ip netns exec LEMON ping 10.10.10.3

# 참고로 매번 네임스페이스 exec 를 명시하지 않고,
# nsenter 명령으로 네임스페이스 들어가 있을 수 있다.
# 들어간 네임스페이스에 exit 를 하면 빠져나온다.
nsenter --net=/var/run/netns/LEMON
ping 10.10.10.3

# 통신시 ARP 로 매번 브로드캐스팅은 비효율적이라 캐싱하게 된다.
# LEMON 네임스페이스 상에서 이웃을 조회하면 10.10.10.3(apple_eth0)가 캐싱되있다.
ip neighbor

# 현재 네임스페이스를 빠져나온다.(호스트(루트) 네임스페이스로 이동)
exit

# 이제 LEMON, APPLE 네임스페이스 삭제하면 LEMON, BLUE 네임스페이스 안의 이더넷도 모두 삭제된다.
ip netns del LEMON
ip netns del APPLE


##### iptables(netfilter) 설정하기 #####


# iptables 은 커널의 netfilter(프레임워크)에 rules를 설정하고 실제로는 netfilter 로 방화벽 기능이 동작한다.
# netfilter 는 다음 5개의 chain 을 hook 하고 룰에 따라 패킷을 필터링한다.
# PREROUTING: 외부로부터 들어오는 패킷
# INPUT: PREROUTING -> 로컬에서 받아야 하는 패킷
# FORWARD: PREROUTING -> FORWARD -> POSTROUTING 으로 로컬을 거치지 않고 외부로 부터 들어온 패킷을 다른 외부로 가야하는 패킷
# OUTPUT: 로컬 -> POSTROUTING 으로 로컬에서 외부로 가는 패킷
# POSTROUTING: FORWARD 또는 로컬에서 외부로 나가야하는 패킷

# 각 chain 에 맞는 다음과 같은 성격의 룰 테이블이 있다.
# filter:  패킷 필터링에 사용하는 테이블, INTPUT,FORWARD,OUTPUT 에서 사용
# nat: 출발지, 목적지 변경시 사용하는 테이블, PREROUTING,POSTROUTING 에서 사용
# mangle: 패킷 변조시 사용하는 테이블�, 5개 chain 모두에서 사용
# raw: 주로 netfilter 에서 추적하지 않도록 NOTRACK 설정하는데 사용하는 테이블, PREROUTING,OUTPUT 에서 사용
# security: SELinux 보안 사용시 사용하는 테이블, INPUT,FORWARD,OUTPUT 에서 사용

# filter 테이블 -S(list-rules) 룰 리스트 보기
iptables -t filter -S 
# -t 명시하지 않으면 filter 가 기본값이다.
iptables -S

# FOWARD chain에 설정된 rule들 보기 -v(가 없으면 in,out 이더넷 장치가 보인다) 
iptables -L FORWARD -v
iptables -L INPUT -v

# 10.10.10.3 으로 부터 들어오는(INPUT chain) -j(--jump) DROP(패킷 차단) 룰 추가(-A, --append)
iptables -A INPUT -s 10.10.10.3/32 -j DROP

# 10.10.10.3 으로 부터 들어오는(INPUT chain) -j(--jump) ACCEPT(패킷 허용) 룰 추가(-A, --append)
iptables -A INPUT -s 10.10.10.3/32 -j ACCEPT

# 룰 삭제(-D, --delete)
iptables -D INPUT -s 10.10.10.3/32 -j ACCEPT

# -A 같은 내용의 룰이 계속 추가 되 때문에
# -D 같은 내용이라도 마지막 룰만 삭제되어 모두 삭제하려면 N번 실행해야 한다.

# 외부(인터넷) 통신을 위해서는
# POSTROUTING 에 nat 추가
iptables -t nat -A POSTROUTING -s 10.10.10.0/24 -j MASQUERADE
# 패킷을 내 사설IP 에서 받을 수 있도록 -d(--destination) 허용
iptables -t filter -A FORWARD -d 10.10.10.3/24 -j ACCEPT

# nat 설정 상태 확인
iptables -t nat -S

ls dot file with glob, create/delete dash file

# .(dot, hidden) 파일을 *(glob) 으로 bak 으로 끝나는 파일을 조회시
# 다음과 같이 하면 조회가 안된다.
ls *bak
zsh: no matches found: *bak
# 모든 파일 보기 옵션인 -a 옵션을 사용해도 glob 으로는 안된다.
ls -a *bak
zsh: no matches found: *bak

# . 파일을 glob 으로 조회할때면 다음과 같이 . 으로 시작한 후 glob 을 사용해야 한다.
ls .*bak
exa .*bak
lsd .*bak

#####

# dash('-') 로 시작하는 파일명 생성
# -- 이후부터는 - 옵션이 없음
touch -- '-a -b -c'

# 또는
touch ./'-a -b -c'

# - 로 시작하는 파일명 삭제
rm -- '-a -b -c'

# 또는
rm ./'-a -b -c'

uptime cpu load high 찾기

# uptime 확인시 2년 넘게 운영되는 서버가
# system load average (cpu) 가 높게 나오고 계속 올라간다.
# top, htop 에서 프로세스들의 cpu 사용률 확인해 봤지만
# 아래 그림과 같이 높은 cpu 를 사용하는 프로세스는 보이지 않았다.
# 대신 cpu 1개가 계속 100% 상태다.


# 하지만 다음과 같이 pcpu(%cpu) 항목 보기로 cpu 사용률정렬(-r)해서 보면 보인다.
ps -eo %cpu,pid,user,args -r | head -3
%CPU   PID USER     COMMAND
 99    895 root     /sbin/rngd -f
 8.1 30810 root     /usr/lib/systemd/systemd-journald
 0.9 30738 root     htop

# 참고로 맥에서는 -f 기본에 %cpu 컬럼추가해서 봐도 된다.
ps -ef -o %cpu -r | head -3

# htop 에서 CPU% 가 99%인 /sbin/rngd 가 ps 로 보면 높은 cpu 사용률을 보인다.
# 찾아보니 rngd 는 Check and feed random data from hardware device to kernel 로 위와 같은 상황시 재부팅해야한다고 한다.
# 커널 3.10.0-514.21 부터 /dev/hwrnd 가 생겼고
# 기존 rngd 에서 에러가 발생할 수 있어 업데이트가 필요하다고 한다.


도커 컨테이너에서 make -j 옵션

# 6cores(12threads), 32GB RAM 맥북에서
# 도커 컨테이너를 띄우고 c++ make 속도를 높이기 위해
# make -j 4 -> make -j 8 로 같이 병렬 컴파일 잡 개수를 높였다.
# 그런데 다음과 같은 에러가 발생한다.
g++: internal compiler error: Killed (program cc1plus)
Please submit a full bug report,
with preprocessed source if appropriate.
See <http://bugzilla.redhat.com/bugzilla> for instructions.
make[1]: *** [util/utf8_string_bounder.o] Error 4
make[1]: *** Waiting for unfinished jobs....

# 위와 같은 에러는 빌드시 메모리 부족으로 스왑 파일을 사용하는데
# 스왑파일도 1GB 로 적어 컴파일을 끝낼 수 없어 발생한 에러다.
# 컨테이너의 메모리가 2GB 로 설정되어 있었다.
# 컨테이너에서 사용할 cpu, memory 를 늘려야 한다.(스왑파일도 늘리 수 있다.)
# 도커 setting -> resources -> advanced 에서 변경한다.


# 이제 컨테이너를 run 하고
docker run --name ysoftman_centos -dit ysoftman/centos

# 컨테이너에 접속해서
docker exec -u root -e COLUMNS=$(tput cols) -e LINES=$(tput lines) -it ysoftman_centos /bin/bash

# cpu, memroy 를 확인해 보면
# cpu 6, memory 8G 로 설정된것을 볼 수 있다.
cat /proc/cpuinfo  | \grep processor
processor : 0
processor : 1
processor : 2
processor : 3
processor : 4
processor : 5

free -h
              total        used        free      shared  buff/cache   available
Mem:           7.8G        326M        7.1G        868K        384M        7.2G
Swap:          1.0G          0B        1.0G

# 이제 make -j 8 을 수행하면 잘 된다.
time make -j8

# 컨테이너 리소스 증가에 따라 make 빌드 소요 시간도 줄지만
# 같은 리소스상태에서는 -j 값이 어느정도 부터는 시간이 더 이상 줄지 않는다.
# 2CPUs, 2GB mem, make -j 8 --> (2:12초)
# 6CPUs, 8GB mem, make -j 8 --> (1:42초)
# 10CPUs, 16GB mem, make -j 10 --> (1:39초)
# 10CPUs, 16GB mem, make -j 12 --> (1:39초)

# 참고
# make -j 옵션 사용시 값을 명시하지 않으면 무한대로 설정된다.
make --help | \grep  '\-j'
  -j [N], --jobs[=N]          Allow N jobs at once; infinite jobs with no arg.

# 무한대로 하면 모든 cpp 파일이 동시 컴파일 되고 더 많은 메모리를 필요로 해서
# 에러가 또 발생할 수 있어 memory 리소스에 따른 적절한 -j 수치를 정해야 한다.

httpd AH00141 에러

# 201907 현재 최신 버전 apache httpd 사용해서 빌드했다.
# httpd 2.4.39
# apr 1.7.0
# apr-util 1.6.1
# httpd 빌드시 --with-apr=${HOME}/apr 로 옵션사용
# 빌드 후 httpd 실행시 특정 서버에서 다음과 같은 에러가 발생한다.
[:crit] [pid 478] (38)Function not implemented: AH00141: Could not initialize random number generator

# 잘되는 서버(A)
# 에러 발생하는 서버(B)

# 확인 1
# apr 문제로 보고 잘되는 서버와 libapr-1.so 가 사용하는 shared library 를 살펴봤다.
# libuuid.so 사용 여부가 다르긴 하다.
# B 서버에서는 make 에서 -luuid 가 추가된다.
# A 서버
ldd libapr-1.so
linux-vdso.so.1 =>  (0x00007fff695fc000)
librt.so.1 => /lib64/librt.so.1 (0x00007ff7836ee000)
libcrypt.so.1 => /lib64/libcrypt.so.1 (0x00007ff7834b6000)
libpthread.so.0 => /lib64/libpthread.so.0 (0x00007ff78329a000)
libdl.so.2 => /lib64/libdl.so.2 (0x00007ff783096000)
libc.so.6 => /lib64/libc.so.6 (0x00007ff782cd2000)
libfreebl3.so => /lib64/libfreebl3.so (0x00007ff782acf000)
/lib64/ld-linux-x86-64.so.2 (0x00005560922d1000
# B 서버
ldd libapr-1.so
linux-vdso.so.1 =>  (0x00007ffcd53f8000)
libuuid.so.1 => /lib64/libuuid.so.1 (0x00007f8dc4c56000)
librt.so.1 => /lib64/librt.so.1 (0x00007f8dc4a4d000)
libcrypt.so.1 => /lib64/libcrypt.so.1 (0x00007f8dc4816000)
libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f8dc45fa000)
libdl.so.2 => /lib64/libdl.so.2 (0x00007f8dc43f5000)
libc.so.6 => /lib64/libc.so.6 (0x00007f8dc4032000)
/lib64/ld-linux-x86-64.so.2 (0x00007f8dc50a6000)
libfreebl3.so => /lib64/libfreebl3.so (0x00007f8dc3e2f000)

# 확인 2
# A -> B  로 libapr-1.so.0.7.0(libapr-1.so 가 링크)를 복사했지만 에러가 발생한다.
# B -> A  로 libapr-1.so.0.7.0(libapr-1.so 가 링크)를 복사했지만 실행된다.
# 그래서 apr 빌드의 문제는 아니다.

# 확인 3
# libapr 에 포함된 함수중에 random 관련 함수가 실행될 수 없는 문제인것 같다.
nm libapr-1.so | grep random
42:000000000023aed0 b all_random
218:0000000000023d90 T apr_generate_random_bytes
442:000000000002a460 T apr_random_add_entropy
443:000000000002a370 T apr_random_after_fork
444:000000000002a750 T apr_random_barrier
445:000000000002a0c0 t apr_random_bytes
446:000000000002a210 T apr_random_init
447:000000000002a730 T apr_random_insecure_bytes
448:000000000002a780 T apr_random_insecure_ready
449:000000000002a710 T apr_random_secure_bytes
450:000000000002a760 T apr_random_secure_ready
451:000000000002a400 T apr_random_standard_new
912:000000000002a070 t random_cleanup

# 확인 4
# apr v1.7.0 설치 디렉토리의 lib 를 잠깐 lib2 로 변경해 놓으면 실행된다.
# httpd 빌드시 사용된 라이브러리 및 옵션을 확인해봤다.
# 실제 서버에서 로딩하는 APR 버전이 빌드시 사용된 APR 버전보다 낮은
# centos 기본 내장된 /usr/lib64/libapr-1.so.0.4.8 을 사용하는것으로 보인다.
./httpd -V
Server version: Apache/2.4.39 (Unix)
Server built:   Jul 16 2019 18:31:52
Server's Module Magic Number: 20120211:84
Server loaded:  APR 1.4.8, APR-UTIL 1.6.1
Compiled using: APR 1.7.0, APR-UTIL 1.6.1
Architecture:   64-bit
Server MPM:     prefork
  threaded:     no
    forked:     yes (variable process count)
Server compiled with....
 -D APR_HAS_SENDFILE
 -D APR_HAS_MMAP
 -D APR_HAVE_IPV6 (IPv4-mapped addresses enabled)
 -D APR_USE_PROC_PTHREAD_SERIALIZE
 -D APR_USE_PTHREAD_SERIALIZE
 -D SINGLE_LISTEN_UNSERIALIZED_ACCEPT
 -D APR_HAS_OTHER_CHILD
 -D AP_HAVE_RELIABLE_PIPED_LOGS
 -D DYNAMIC_MODULE_LIMIT=256
 -D HTTPD_ROOT="/home/ysoftman/apache-2.4.39"
 -D SUEXEC_BIN="/home/ysoftman/apache-2.4.39/bin/suexec"
 -D DEFAULT_PIDLOG="logs/httpd.pid"
 -D DEFAULT_SCOREBOARD="logs/apache_runtime_status"
 -D DEFAULT_ERRORLOG="logs/error_log"
 -D AP_TYPES_CONFIG_FILE="conf/mime.types"
 -D SERVER_CONFIG_FILE="conf/httpd.conf"

# 결론
# 구글링 해보면 환경변수 PATH 에 깨진 문자가 포함되었거나
# 커널에서 제공하는 랜럼 디바이스 파일 /dev/random, /dev/urandom 이 없는 경우
# 라고하는데 두 경우 특별한 문제점을 찾을 수 없었다.
# 대신 apr 1.5.2 버전을 다운받아 빌드하면 잘 동작한다.
# 참고로 나와 같은 에러가 발생한다는 버그 리포팅이 있다.
https://bz.apache.org/bugzilla/show_bug.cgi?id=63388

mutt 터미널 메일

# mutt 로 터미널상에서 메일을 받고, 보낼 수 있고 한글지원도 잘 된다.
# mutt 설치는 간단하다.
# mac
brew install mutt

# centos, ubuntu
sudo yum install mutt
sudo apt-get install mutt

# -s (subject)
# 메일 내용 "냉무"
# ysoftman@ysotman.com 으로 메일로 보낸다.
echo "냉무" | mutt -s "mutt test 메일입니다." ysoftman@ysoftman.com

# 내용이 길다면 파일로 작성해서 다음처럼 보낼 수 도 있다.
# -a 옵션을 주면 파일 첨부도 가능하다.
mutt -s "mutt test 메일입니다." ysoftman@gmail.com < msg.txt

# 별도의 설정이 없어 다음과 같은 로컬 호스트 주소로 메일이 간다.
# 때문에 대부분 스팸처리된다.
ysoftman <ysoftman@macbook-pro.local>

# mutt 가 사용할 메일함 디렉토리 생성
sudo mkdir -p /var/mail/ysoftman

# mutt 설정 파일을 만들자.
vi ~/.muttrc
set realname="ysoftman"
set imap_user="ysoftman@gmail.com"

#set imap_pass="password" # 명시하지 않으면 프롬프트가 뜬다.
set folder="imaps://imap.gmail.com:993/" # 메일상자들의 기본 폴더
set spoolfile="+inbox" # 기본 메일 상자

#set smtp_url="smtp://ysoftman@smtp.gmail.com:587/"
set smtp_url="smtps://ysoftman@smtp.gmail.com:465/"

#set smtp_pass="password" # 명시하지 않으면 프롬프트가 뜬다.
# gmail 발송시 인증자 없을 에러가 있는 경우 gssapi:login 인증방식을 사용해야 한다.
set smtp_authenticators="gssapi:login"
set ssl_starttls=yes # 서버가 tls 직원하면 활성화
set ssl_force_tls=yes # 항상 ssl 로 연결
set editor="vim"
set charset="utf-8"
set mail_check=30 # 새매일 체크 주기
set imap_check_subscribed=yes
set imap_keepalive=30
set header_cache="$HOME/mail" # 메일 헤더 캐싱 위치
set message_cachedir="$HOME/mail" # 메일 내용 캐싱 위치
set sidebar_visible=no # 사이드바 안보기
set sort="threads" # 메일 쓰레드 보기

# 단축키 설정
bind index G imap-fetch-mail

# theme 적용
source ~/.mutt/dracula.muttrc

# dracula theme 설치
mkdir -p ~/.mutt && cd ~/.mutt
git clone https://github.com/dracula/mutt.git
cp -v ~/.mutt/mutt/dracula.muttrc ~/.mutt
rm -rfv ~/.mutt/mutt/

# mutt 실행 중 발생되는 메시지 디버깅 기록(~/.muttdebug0)
# 1~5 로 높을수록 자세한 정보가 남는다.
mutt -d 2

# 참고로 mutt 으로 gmail 접속하려면 앱 비밀번호를 발급받아 사용해야 한다.
https://myaccount.google.com/security -> 앱 비밀번호

# mutt 자주쓰는 키(vim 과 비슷하다)
? 도움말
/ 검색
! 셀명령 실행
: muttrc 에서 사용하는 명령 실행
q 현재화면 종료
j 메일 리스트에서 다음항목으로 이동, 메일내용보기에서 다음메일 보기(아래쪽,오른쪽방향키)
k 메일 리스트에서 이전항목으로 이동, 메일내용보기에서 이전메일 보기(위쪽,왼쪽방향키)
숫자 해당메일로 이동
tab 다음 새메일로 이동
N 새메일/읽은메일 플래그 토글
h 메일보기에서 헤더등 자세한 정보 토글
m 새메일 작성
r 현재 메일 답장
d 메일 삭제
(삭제메일로 표시되며, mutt 종료시 삭제여부를 묻는다.)
u 메일 복구(삭제메일 이전 항목에서 u하면 아래항목의 메일이 복구된다.)
e 저장된 메일 편집
home 메일 리스트에서 첫번째 항목으로 이동
end 메일 리스트에서 마지막 항목으로 이동

네트워크 관련 sysctl

# 연결 지향 tcp 는 데이터를 주고 받기전 우선 연결된다.
# 리슨을 위해 바인딩된 포트 열어 놓는 쪽은 passive open으로 서버가 되고,
# 연결을 요청하는 쪽은 active open으로 클라가 된다.
# tcp 종료 과정은 다음과 같다.
# 그림 출처 : 위키피디아 https://en.wikipedia.org/wiki/Transmission_Control_Protocol


# sysctl 은 런타임시에 커널의 파리미터를 설정하는 명령어다.
# /etc/sysctl.conf 파일로도 설정할 수 있다.
# /proc/sys/net/ipv4 에 파일 수정으로 설정할 수 있다.
https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt
http://lxr.linux.no/linux+v3.2.8/Documentation/networking/ip-sysctl.txt
# 이중 net.ipv4 로 시작하는 tcp 제어 설정에서 중요한 몇개를 살펴 보자.

# 로컬 포트가 고갈되는 경우
# TIME_WAIT(fin을 받고 서버에 ack 보내고 일정 시간 기다리는 상태)의 소켓들을 재사용할지 여부
# 0 : 비활성화
# 1 : 활성화 (보통 1로 사용)
# 2 : loopback 에서만 활성화
net.ipv4.tcp_tw_reuse = 1

# FIN_WAIT_2(서버쪽 close 중임을 클라에게 알리는 ack 를 받았을때의 클라 상태)이후
# last_ack 를 받지 못했을때 대기시간으로 디폴트 60초이다.
# 참고로 TIME_WAIT 의 타임아웃은 커널소스에 60초로 하드 코딩되어 있다.
# /usr/src/kernels/3.10.0-957.1.3.el7.x86_64.debug/include/net/tcp.h
# #define TCP_TIMEWAIT_LEN (60*HZ)
net.ipv4.tcp_fin_timeout = 60

# 로컬 포트 사용(허용) 범위
net.ipv4.ip_local_port_range = 23768 60999

# tcp_tw_use 처럼 TIME_WAIT 상태의 소켓을 재사용(recycle)할지 여부
# 디폴트는 비활성화
# 여러 문제가 발생될 소지가 있어 최근 리눅스 4.12 에서는 제거되었다고 한다.
https://vincent.bernat.ch/en/blog/2014-tcp-time-wait-state-linux
net.ipv4.tcp_tw_recycle = 0

Linux 서버시간 동기화

# date 로 현재 시간확인
date

# date 로 특정 시간으로 설정
sudo date -s "2010-10-12 15:30:00"

# 실제 시간으로 복귀(서버 시간 동기화)
rdate -s time.bora.net
# 또는
rdate -s time.nist.gov

# 위 rdate 가 timeout 발생되면서 실패한다면 ntp(Network Time Protocol) 를 사용해보자.
# ntp 설치
sudo yum install ntp

# ntp.conf 에서 동기화할 서버를 명시한다.
sudo vi /etc/ntp.conf

# ntpd 서비스 실행
# 서비스는 시작 후 5 분 정도 후 sync 가 실행된다.
sudo chkconfig ntpd on
sudo service ntpd restart

# npt 서버 통신 상태
ntpd -q

# 참고
# 하드웨어 시간 보기
sudo hwclock

# 하드웨어 시간을 현재 시스템 시간으로 변경
sudo hwclock -w

bash 환경에서 명령줄 vi 스타일로 편집하기

# bash(or zsh)에서 명령줄을 vi 스타일로 편집할 수 있다.
# 다음 명령으로 실행하면
set -o vi

# vi 옵션이 활성화(on)된것을 확인할 수 있다.
set -o | grep vi

# 명령줄을 입력하다가 esc 로 명령모드로 전환해 w, b 로 워드단위로 이동할 수 있다.
# a, i 로 입력모드로 돌아 입력할 수 있다.
# v 로 블럭 지정등 기타 vi 편집 기능을 사용할 수 있다.

# vi 옵션을 비활성화(off)하려면
set -o novi

# 참고
http://linuxcommand.org/lc3_man_pages/seth.html