Simulation is ...

Virtual = Real

Flight Sim 자세히보기

ㆍ Linux

udev, user device, systemd, failed to get udev event, udev rule

FlightSim 2023. 8. 14. 17:39
320x100

Linux에서 장치 감지 및 관리를 위해 Udev를 사용하는 방법


Udev(사용자 공간 /dev)는 커널 버전 2.6부터 동적 장치 감지 및 관리를 위한 Linux 하위 시스템입니다. devfs  hotplug를 대체합니다.

장치 노드(/dev 디렉토리에 저장된 일반 파일인 것처럼 파일 시스템에 나타나는 장치 드라이버에 대한 인터페이스)를 부팅 시 또는 추가할 때 동적으로 생성하거나 제거합니다. 장치를 시스템에 연결하거나 시스템에서 장치를 제거합니다. 그런 다음 장치에 대한 정보 또는 상태 변경 사항을 사용자 공간에 전파합니다.

그 기능은 1) 시스템 애플리케이션에 장치 이벤트를 제공하고, 2) 장치 노드의 권한을 관리하고, 3) 장치에 액세스하기 위해 /dev 디렉토리에 유용한 심볼릭 링크를 만들거나 네트워크 인터페이스의 이름을 바꾸는 것입니다. .

udev의 장점 중 하나는 검색 순서에도 불구하고 재부팅 시 일관된 장치 이름 지정을 보장하기 위해 영구 장치 이름을 사용할 수 있다는 것입니다. 이 기능은 커널이 검색 순서에 따라 예측할 수 없는 장치 이름을 할당하기 때문에 유용합니다.

이 기사에서는 Linux 시스템에서 장치 감지 및 관리를 위해 Udev를 사용하는 방법을 알아봅니다. 전부는 아니지만 대부분의 최신 Linux 배포판은 기본 설치의 일부로 Udev와 함께 제공됩니다.

 

Linux에서 Udev의 기초 배우기

udev 데몬 systemd-udevd(또는 systemd-udevd.service)는 커널과 통신하고 기기 uevents를 수신합니다. > 시스템에서 장치를 추가 또는 제거하거나 장치의 상태가 변경될 때마다 장치에서 직접.

Udev는 규칙을 기반으로 합니다. 규칙은 유연하고 매우 강력합니다. 수신된 모든 장치 이벤트는 /lib/udev/rules.d  /run/udev/rules.d에 있는 파일에서 읽은 규칙 집합과 일치합니다.

/etc/udev/rules.d/ 디렉토리에 사용자 지정 규칙 파일을 작성하여 장치를 처리할 수 있습니다(파일은 .rules 확장자로 끝나야 함). 이 디렉터리의 규칙 파일이 가장 높은 우선 순위를 가집니다.

장치 노드 파일을 생성하려면 udev 레이블, 일련 번호, 주 번호 및 부 번호와 같은 특정 속성을 사용하여 장치를 식별해야 합니다. 사용, 버스 장치 번호 등. 이 정보는 sysfs 파일 시스템에서 내보냅니다.

장치를 시스템에 연결할 때마다 커널이 이를 감지하고 초기화하며 장치 속성을 저장하는 /sys/ 디렉토리 아래에 장치 이름을 가진 디렉토리가 생성됩니다.

udev의 기본 구성 파일은 /etc/udev/udev.conf이며 udev 데몬의 런타임 동작을 제어하려면 udevadm 유틸리티를 사용할 수 있습니다.

수신된 커널 이벤트(uevents) 및 udev 이벤트(udev가 규칙 처리 후 전송)를 표시하려면 udevadm 모니터 명령으로. 그런 다음 장치를 시스템에 연결하고 터미널에서 장치 이벤트가 처리되는 방식을 확인합니다.

다음 스크린샷은 USB 플래시 디스크를 테스트 시스템에 연결한 후 ADD 이벤트의 일부를 보여줍니다.

$ udevadm monitor 

USB 디스크에 할당된 이름을 찾으려면 sysfs 파일 시스템과 udev db를 읽는 lsblk 유틸리티를 사용하여 처리된 장치에 대한 정보를 수집합니다.

 
$ lsblk

이전 명령의 출력에서 USB 디스크 이름은 sdb1입니다(절대 경로는 /dev/sdb1여야 함). udev 데이터베이스에서 장치 속성을 쿼리하려면 info 명령을 사용하십시오.

$ udevadm info /dev/sdb1

Linux에서 Udev 규칙으로 작업하는 방법

이 섹션에서는 udev 규칙을 작성하는 방법에 대해 간략하게 설명합니다. 규칙은 하나 이상의 키-값 쌍의 쉼표로 구분된 목록으로 구성됩니다. 규칙을 사용하면 장치 노드의 이름을 기본 이름에서 변경하고, 장치 노드의 권한 및 소유권을 수정하고, 장치 노드가 생성되거나 삭제될 때 프로그램 또는 스크립트 실행을 트리거할 수 있습니다.

USB 장치가 추가될 때와 실행 중인 시스템에서 제거될 때 스크립트를 시작하는 간단한 규칙을 작성합니다.

두 개의 스크립트를 작성하여 시작하겠습니다.

$ sudo vim /bin/device_added.sh

device_added.sh 스크립트에 다음 줄을 추가합니다.

#!/bin/bash
echo "USB device added at $(date)" >>/tmp/scripts.log

두 번째 스크립트를 엽니다.

$ sudo vim /bin/device_removed.sh

그런 다음 device_removed.sh 스크립트에 다음 줄을 추가합니다.

#!/bin/bash
echo "USB device removed  at $(date)" >>/tmp/scripts.log

파일을 저장하고 닫은 다음 두 스크립트를 실행 가능하게 만드십시오.

$ sudo chmod +x /bin/device_added.sh
$ sudo chmod +x /bin/device_removed.sh

다음으로 /etc/udev/rules.d/80-test.rules라는 위 스크립트의 실행을 트리거하는 규칙을 생성해 보겠습니다.

$ vim /etc/udev/rules.d/80-test.rules

다음 두 가지 규칙을 추가하십시오.

SUBSYSTEM=="usb", ACTION=="add", ENV{DEVTYPE}=="usb_device",  RUN+="/bin/device_added.sh"
SUBSYSTEM=="usb", ACTION=="remove", ENV{DEVTYPE}=="usb_device", RUN+="/bin/device_removed.sh"

어디:

  • \==\: 같은지 비교하는 연산자입니다.
  • \+=\: 항목 목록을 보유하는 키에 값을 추가하는 연산자입니다.
  • SUBSYSTEM: 이벤트 장치의 하위 시스템과 일치합니다.
  • ACTION: 이벤트 액션의 이름과 일치합니다.
  • ENV{DEVTYPE}: 기기 속성 값(이 경우 기기 유형)과 일치합니다.
  • RUN: 이벤트 처리의 일부로 실행할 프로그램 또는 스크립트를 지정합니다.

파일을 저장하고 닫습니다. 그런 다음 루트로 실행하여 규칙 파일을 다시 로드하도록 systemd-udevd에 지시합니다(이는 커널 모듈 색인과 같은 다른 데이터베이스도 다시 로드함).

$ sudo udevadm control --reload

이제 USB 드라이브를 컴퓨터에 연결하고 device_added.sh 스크립트가 실행되었는지 확인합니다. 먼저 scripts.log 파일이 /tmp 아래에 생성되어야 합니다.

$ ls -l /tmp/scripts.log

그러면 파일에 스크린샷과 같이 \date_time에 제거된 USB 장치”와 같은 항목이 있어야 합니다.

$ cat /tmp/scripts.log

udev 규칙을 작성하고 udev를 관리하는 방법에 대한 자세한 내용은 다음을 실행하여 각각 udev  udevadm 매뉴얼 항목을 참조하십시오.

$ man udev
$ man udevadm

요약

Udev /dev 디렉토리에서 장치 노드를 설정하는 동적인 방법을 제공하는 뛰어난 장치 관리자입니다. 장치가 연결되고 검색되는 즉시 장치가 구성되도록 합니다. 처리된 장치에 대한 정보 또는 상태 변경 사항을 사용자 공간에 전파합니다. 

 한편 kernel build를 해보면 알겠지만, kernel쪽 driver폴더를 살펴보면 그 종류가 어마어마하게 많은 것을 알 수 있다. 지금이야 더 많겠지만, kernel 2.4에서의 driver는 약 15000~20000개 정도였다고 한다. 물론 아주 일반적인 driver는 kernel에 포함되어 있겠지만, 그게 아니라면 15000여개라는 숫자는 한꺼번에 담기에는 조금 큰 숫자이다. 물론 이렇게 사이즈가 커버리면 리눅스가 추구하는 Portability, 특히 embedded Linux같은 경우는 큰 영향을 받는다. 따라서 driver는 보통 아주 필수적인 케이스를 제외하고는 수동적으로 선택되며, 자칫하다가는 오류를 야기할 수도 있다. 

 이러한 목적에서 udev라는 것이 나왔다. udev를 사용하면 device node를 사용자가 원하는대로 생성할 수 있어서 앞에서 소개했던 예와 같이 수많은 device를 위한 node를 가지지 않아도 된다. 또한 어떤 node에 대한 생성, 삭제, 수정등이 모두 user level에서 이뤄질 수 있기 때문에 관리상 이점도 있다. 그리고 node 자체를 booting할때뿐만 아니라 dynamic 하게 생성할 수 있기 때문에 devfs나 hotplug 같은 용도로 사용할 수 있다. 이런 동작에 대한 규칙은 udev-rules 라는 이름으로 kernel내에서 가지고 있다.

 udev는 우선 udevd 혹은 systemd-udevd라는 이름의 데몬으로 동작하며, netlink socket를 통해서 device node 동작들을 모니터링한다. 만약 새로운 device가 생성되거나 제거되는 등의 동작이 발생할 경우 uevent 라는게 kernel 내에서 발생해서 udevd에게 해당 동작에 대한 정보를 전달한다. 그러면 udev는 udev rule에 정의되어 있는 device node와 property에 맞는 동작을 취하게 된다.

 udev framework를 구성하는 것은 크게 세가지가 있다.

 - libudev : device에 대한 정보를 접근할 수 있도록 해주는 library

 - udevd : /dev 하위의 device node들을 관리하는 daemon

 - udevadm : device node에 대한 control 및 diagnostic를 관장하는 utility

 설정에 대해서 언급을 하자면 udev에 대한 주요 configuration file은 /etc 밑에 있다.

/etc/udev 밑에 해당 configuration file들이 있는데 여기서 유심히 봐야할 것이 rules.d 와 udev.conf 이다. rules.d에 있는 내용이 바로 앞에서 언급했던 udev rule, 즉 device node에 대한 정보를 가지고 있는데, 보통은 device에 대한 naming이나 별도의 동작 등이 정의되어 있다. 그리고 일반적으로 udev.conf에는 device node의 생성 지점과 권한등이 지정되는데, 여기서는 별 내용이 들어있지 않았다.  참고로 위 이미지에서도 나오지만 udev는 initramfs에서 파생된 daemon이기 때문에 rule이 바뀌거나 udev 환경이 바뀔 경우 initramfs 역시 그 환경에 맞춰서 바뀌어야 한다.

 참 rules.d 안에 udev rule은 다음과 같이 되어 있는데, 여기에도 일종의 format이 있다. 이런게 있다정도로만 알아두면 좋을 듯 싶다.

https://talkingaboutme.tistory.com/entry/Linux-udev-user-device

 

Udev 란

Udev 는 리눅스 커널을 위한 장치 관리자이다. devfsd와 hotplug를 계승하는 udev는 주로 /dev 디렉터리의 장치 노드를 관리한다.
CentOS/RHEL/OL/Rocky 에서 systemd-udevd daemon 으로 동작된다.

장치 정보 상세 보기

Udev Rule 을 사용하기 위해선 udevadm 명령을 통해 장치의 상세 정보를 확인해야된다.

아래는 장치의 기본 정보를 확인하는 방법이다.

# udevadm info /dev/sdb
P: /devices/pci0000:80/0000:80:08.3/0000:83:00.0/ata2/host1/target1:0:0/1:0:0:0/block/sdb
N: sdb
S: disk/by-id/ata-Samsung_SSD_860_PRO_2TB_S5Gqwerasdfzxfc
S: disk/by-id/wwn-0x500123456789012
S: disk/by-path/pci-0000:83:00.0-ata-2.0
E: DEVLINKS=/dev/disk/by-id/ata-Samsung_SSD_860_PRO_2TB_S5Gqwerasdfzxfc /dev/disk/by-id/wwn-0x500123456789012 /dev/disk/by-path/pci-0000:83:00.0-ata-2.0
E: DEVNAME=/dev/sdb
E: DEVPATH=/devices/pci0000:80/0000:80:08.3/0000:83:00.0/ata2/host1/target1:0:0/1:0:0:0/block/sdb
E: DEVTYPE=disk
...생략

P 는 DEVPATH,
N 는 NAME,
S 는 SUBSYSTEM,
E 는 ENV 를 의미한다.

  • 참고: man udev
       # man udev
       ... 생략
         SUBSYSTEM
             Match the subsystem of the event device.
         DEVPATH
             Match the devpath of the event device.
         ENV{key}
             Match against a device property value.
         ATTR{filename}
             Match sysfs attribute values of the event device. Trailing whitespace in the attribute values is ignored unless the specified match value itself contains trailing whitespace.
         NAME
             Match the name of a network interface. It can be used once the NAME key has been set in one of the preceding rules.
         KERNEL
             Match the name of the event device.
    

아래는 장치의 상세 정보 확인 방법이다.

# udevadm info -a /dev/sdb
 
Udevadm info starts with the device specified by the devpath and then
walks up the chain of parent devices. It prints for every device
found, all possible attributes in the udev rules key format.
A rule to match, can be composed by the attributes of the device
and the attributes from one single parent device.

  looking at device '/devices/pci0000:80/0000:80:08.3/0000:83:00.0/ata2/host1/target1:0:0/1:0:0:0/block/sdb':
    KERNEL=="sdb"
    SUBSYSTEM=="block"
    DRIVER==""

...생략

  looking at parent device '/devices/pci0000:80/0000:80:08.3/0000:83:00.0/ata2/host1/target1:0:0/1:0:0:0':
    KERNELS=="1:0:0:0"
    SUBSYSTEMS=="scsi"
    DRIVERS=="sd"
    ATTRS{rev}=="2B6Q"
    ATTRS{type}=="0"
    ATTRS{wwid}=="naa.500123456789012"
    ATTRS{scsi_level}=="6"
    ATTRS{model}=="Samsung SSD 860 "
    ATTRS{state}=="running"
    ATTRS{queue_depth}=="31"

...생략

Udev Rule 작성

장치 상세 정보를 이용하여 아래와 같은 Udev Rule 작성 할 수 있다.

DISK 예제

아래 내용은 Udev rule 을 이용하여 DISK 속성을 설정하는 방법이다.

# vi /etc/udev/rules.d/99-ssd.rules
ACTION=="add|change", SUBSYSTEM=="block", ATTRS{model}=="Samsung SSD*", ATTR{queue/read_ahead_kb}="2048"
ACTION=="add|change", SUBSYSTEM=="block", ATTRS{model}=="Samsung SSD*", RUN+="/sbin/hdparm -W 0 /dev/%k"

위와 같은 방법으로 read_ahead_kb 와 같은 장치의 Parameter 및 명령어를 통한 Write Cache OFF 와 같은 설정을 적용 할 수 있다.
참고로 %k 는 장치의 Kernel name 을 말하는 것이다.

Ethernet Interface 예제

아래 내용은 Udev rule 을 이용하여 Ethernet Interface 이름을 변경하는 방법이다.

# vi /etc/udev/rules.d/70-persistent-network.rules
SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="11:22:33:44:55:66", KERNEL=="ens*", NAME="eth0"

Mac Address 가 11:22:33:44:55:66 이고 장치의 Kernel name 이 ens* 인 장치를 add 하는 ACTION 이 발생될 때, NAME 을 eth0 로 변경하는 Rule 이다.

위와 같은 예제는 기본적으로 udevadm 으로 확인한 정보 기반으로 작성하는 것을 권고한다.

Udev Rule 이 적용이 안되는 경우

Udev Rule 이 정상적으로 적용이 안되는 경우, systemd-udevd 의 로그를 확인하는 것이 좋다.

아래는 Ethernet Interface 변경 할 때, 제일 많이 발생되는 오류이며 systemd-udevd 로그를 통해 확인이 가능하다.

Jan 01 09:00:00 test-node1 systemd-udevd: Error changing net interface name 'eth2' to 'eth1': File exists
Jan 01 09:00:00 test-node1 systemd-udevd: could not rename interface '8' from 'eth2' to 'eth1': File exists

특정 Interface 를 eth1로 이름을 변경하려고 했으나 eth1 의 이름을 Interface ‘8’ 이 사용중이라 변경이 불가능한 이슈이다.
이런 이슈는 eth1 이름을 사용하는 Interface ‘8’ 을 다른 이름으로 변경하고(eth1 이 아닌 다른 이름) eth1 로 변경하고 싶은 Interface 를 변경하면 된다.

쉽게 정리하면 replace 는 불가능하며 name 은 unique 해야된다.

Udev Reload

아래 명령어를 통해 Udev Rule 를 Reload 할 수 있습니다.

# udevadm control --reload

Udev Trigger

아래 명령어를 통해 Udev Rule 를 적용 할 수 있습니다.

# udevadm trigger --action=add 
  or
# udevadm trigger --action=change

참고 자료

 

systemd 살펴보기

우선, systemd가 이렇게 급 부상하게 될 줄은 솔직히 몰랐다. 워낙 많은 오픈소스 프로젝트들이 나오고 있기 때문에 그 중 하나 정도로만 생각했는데 어느 순간 Fedora에 적용이 되더니 당연한 수순대로 RHEL7에 도입이 되었다. 아직 Debian/Ubuntu 계열은 기본으로 채택되지 않았지만 조만간 릴리즈되는 버전에서 새로운 PID 1으로 자리잡게 될 것 같다.

이러한 분위기에 맞추어 systemd가 무엇이고 이에 대해서 간단히 짚어 볼 필요가 있을 듯 하여 간단히 소개해 본다. (늘 그렇듯이 상세한 내용은 공식홈페이지의 문서를 참고하는 것이 좋다)

사실 RHEL6가 등장하고 Upstart가 도입되었을 때는 그저 초기화 스크립트 관리 방안이 바뀌었을 뿐이기에 크게 신경쓰지 않았고 주변에 관련내용 공유도 별로하지 않았다. (설정에 꼭 필요한 방법만 공유했었다) 하지만, systemd의 경우는 Upstart와 비교되지 않는 물건이기 때문에 이렇게 문서까지 작성해서 공유하게 되었다.

systemd는?

systemd가 처음 소개되고 프로젝트가 진행 될 때 커뮤니티 반응은 시끌벅적했다. 우선 systemd는 전통적으로 Unix계열 운영체제의 PID 1이었던 init(System V Init)을 교체하는 역할 뿐만 아니라 초기화 스크립트 관리자이고 로그시스템 관리자이기도 하다. 또한, 하드웨어에 대한 부분과 cgroup 관리 등 시스템 전반적인 부분에 관여하고 있다. 심지어 기존 SysV에서 공통적으로 사용되었던 프로세스 데몬을 만들기 위한 setsid() 콜도 필요없고 PID파일을 따로 관리할 필요도 없다. 이러한 systemd는 유닉스의 철학인 ‘한 가지만 잘하자’와 상반되기 때문에 논란이 되곤 했었다. PulseAudio 개발자라는 이유(커뮤니케이션이 잘 안되기로 유명했다 한다)로 싸우는건 또 다른 논란이었고…

여러 논란가운데 결국 대표적인 배포판에 입성하게 되었는데 논란의 결과가 어찌되었건 납득할 만한 디자인과 성능을 가지고 있기에 채택되지 않았을까 한다.

이 프로젝트의 철학(?)을 알고 싶다면 개발자인 Nennart의 블로그를 읽어보는 것도 좋다.

실제 systemd가 어떠한 물건인지 알아보기 위한 간단한 실습을 위해서 당연히 systemd를 사용중인 배포판을 설치해서 사용해 보는 것이 좋다.

  • RHEL7
  • CentOS 7
  • Fedora
  • OpenSUSE

Debian이나 Ubuntu에서 사용하는 방법은 나중에 시간 될 때 다른 문서를 통해서 공유하겠다. (아마 그 전에 정식채택 될 것 같다. - 관련내용)

실행하며 살펴보기

아래 내용은 RHEL7에서 실행한 내용이며 어느 배포판을 사용하더라도 크게 차이는 없다.

1. PID 1

먼저 ps 명령을 통해서 PID 1 프로세스를 확인해 보자

$ ps -p 1 ef
  PID TTY      STAT   TIME COMMAND
    1 ?        Ss     0:00 /usr/lib/systemd/systemd --switched-root --system --deserialize 24

늘 익숙했던 init 대신에 systemd가 PID 1로 자리잡고 있다.

2. 설정파일

systemd는 /etc/systemd 아래에 설정파일을 두고 있다.

$ ls /etc/systemd
bootchart.conf  journald.conf  logind.conf  system  system.conf  user  user.conf

이러한 설정파일과 시스템에 미리 정의 된 Service, Target 파일을 통해서 시스템을 컨트롤하게 되는데 Service, Target 파일은 아래에 정의 되어있으며 보통 /etc/systemd에서 설정하면서 심볼릭 링크를 통해서 사용한다.

바이너리 실행파일은 아래 경로에서 확인 가능하며
$ ls /lib/systemd/

기본적인 시스템의 Service, Target은 아래에 위치하고 있다
$ ls /lib/systemd/system

3. 부팅시간

systemd는 최소한의 서비스만을 실행시키고 병렬화해서 실행시키는데 주안점을 두고 있기 때문에 기존에 순차적 방식으로 처리하는 SysV에 비해서 부팅속도가 빠른 편이다. RHEL7이 RHEL6보다 부팅속도가 매우 빠른건 systemd 때문이다.

그러면, 부팅에 걸린 시간을 알아보자

$ systemd-analyze
Startup finished in 421ms (kernel) + 1.206s (initrd) + 25.873s (userspace) = 27.501s

총, 27초 정도가 소요 된 것을 확인 할 수 있는데 커널 초기화 작업에는 1초미만 램디스크 초기화에 1.2초 그리고 실제 systemd 프로세스에 의해서 초기화 작업이 진행 된 시간은 26초 정도 이다. 이를 토대로 부팅 시간을 단축 시킬(즉, 불필요한 프로세스가 있는지 여부부터 오동작으로 인해 시간을 많이 잡아먹는지) 방안에 대해서 생각해 볼 수 있다.

$ systemd-analyze blame
         20.732s kdump.service
          1.395s firewalld.service
          1.040s postfix.service
          1.031s lvm2-monitor.service
           997ms tuned.service
           974ms boot.mount
           782ms network.service
           588ms lvm2-pvscan@8:2.service
           571ms iprupdate.service
           571ms iprinit.service
           423ms sshd.service
           348ms systemd-logind.service
           324ms avahi-daemon.service
           312ms iprdump.service
           296ms NetworkManager.service
           269ms rsyslog.service
           192ms systemd-fsck-root.service
           191ms kmod-static-nodes.service
           ... 하략 ...

상기 결과는 가상머신에 올린 게스트 머신이기 때문인지 kdump 서비스가 차지하는 시간이 많은 것으로 나타났다. 이 blame 결과를 통해서 불필요한 서비스를 제거하거나 이상이 있는 서비스를 확인해 볼 수 있다. (Blame Game 참고)

또한, 시간이 많이 소요된 서비스에 대해 실행과 대기에 대해서 체인형태로 확인하는 방법도 있다.

$ systemd-analyze critical-chain
The time after the unit is active or started is printed after the "@" character.
The time the unit takes to start is printed after the "+" character.

multi-user.target @25.865s
└─kdump.service @5.131s +20.732s
  └─network.target @5.130s
    └─network.service @4.346s +782ms
      └─NetworkManager.service @4.049s +296ms
        └─firewalld.service @2.649s +1.395s
          └─basic.target @2.646s
            └─paths.target @2.646s
              └─brandbot.path @2.645s
                └─sysinit.target @2.638s
                  └─systemd-update-utmp.service @2.630s +7ms
                    └─auditd.service @2.517s +111ms
                      └─local-fs.target @2.513s
                        └─boot.mount @1.538s +974ms
                          └─systemd-fsck@dev-disk-by\x2duuid-b4f107ad\x2df256\x2d48b4\x2d9558\x2d24
                            └─dev-disk-by\x2duuid-b4f107ad\x2df256\x2d48b4\x2d9558\x2d2483cbca6a7d.

그 외에 systemd-analyze 툴을 통해서 부팅 과정을 그래프화 해서 볼 수 있으며

$ systemd-analyze dot | dot -Tsvg > systemd.svg
$ systemd-analyze plot > systemd.svg

이러한 부팅 분석 툴만으로도 기존 init에 비해서 프로파일링이 편리해졌음을 확인 할 수 있다.

4. Run Level 변경

systemd는 기존 init 커맨드와 달리 숫자 기반의 런레벨이 아니라 각 런레벨에 대한 설정 세트를 통해서 런레벨을 변경합니다.

싱글모드(기존 런레벨1)

$ systemctl rescue

멀티유저모드(기존 런레벨3)

$ systemctl isolate multi-user.target
$ systemctl isolate runlevel3.target

과거 init 시스템에 익숙한 사용자를 위해서 runlevel3라는 이름으로 multi-user.target 파일을 심볼릭 링크를 걸어두었기 때문에 위 두가지 명령이 모두 사용가능 하다.

$ ls -l /lib/systemd/system/runlevel3.target
lrwxrwxrwx. 1 root root 17 Oct 21 00:28 /lib/systemd/system/runlevel3.target -> multi-user.target

그래픽모드(기존 런레벨5)

$ systemctl isolate graphical.target
$ systemctl isolate runlevel5.target

멀티유저모드와 마찬가지로 2가지 명령으로 전환 가능하며 실제 기존 형태의 런레벨+숫자 형태의 Target 파일은 아래와 같이 심볼릭 링크로 연결되어있다.

lrwxrwxrwx. 1 root root 15 Oct 21 00:28 runlevel0.target -> poweroff.target
lrwxrwxrwx. 1 root root 13 Oct 21 00:28 runlevel1.target -> rescue.target
lrwxrwxrwx. 1 root root 17 Oct 21 00:28 runlevel2.target -> multi-user.target
lrwxrwxrwx. 1 root root 17 Oct 21 00:28 runlevel3.target -> multi-user.target
lrwxrwxrwx. 1 root root 17 Oct 21 00:28 runlevel4.target -> multi-user.target
lrwxrwxrwx. 1 root root 16 Oct 21 00:28 runlevel5.target -> graphical.target
lrwxrwxrwx. 1 root root 13 Oct 21 00:28 runlevel6.target -> reboot.target

즉, 시스템 종료/재부팅을 위한 런레벨도 여전히 사용가능하다.

런레벨 기본 값 설정

상기에서 전환하는 런레벨 Target을 아래와 같은 명령을 통해서 기본 값으로 설정 할 수 있다. 또한, 현재 설정된 기본 값을 확인 할 수도 있다

$ systemctl set-default multi-user.target
$ systemctl get-default
multi-user.target

시스템 명령

앞서 각각의 런레벨 파일이 poweroff.target 등 으로 연결되어있는 것을 확인하였는데 isolate 명령이 아닌 시스템 명령을 통해서 해당 Target을 바로 적용하는게 가능하다. 아래는 몇 가지 예시이다.

$ systemctl poweroff (Shutdown처리 후 Power-Off처리)
$ systemctl emergency (Rescue와 유사하지만 root 파일시스템만 읽기전용으로 마운트한다)
$ systemctl halt (Shutdown처리 후 Halt처리)
$ systemctl reboot (Shutdown처리 후 리부팅처리)
$ systemctl kexec (kexec를 통해서 리부팅한다)
$ systemctl suspend (시스템 정지)
$ systemctl hibernate (시스템 Hibernate)
$ systemctl hybrid-sleep (시스템을 Hibernate하고 정지시킨다)

5. 서비스 목록

systemctl을 통해서 서비스를 관리 할 수 있는데 먼저 서비스 목록 확인 방법을 알아보자.

서비스 목록은 간단하게 systemctl 명령만 실행해도 확인 할 수 있으며 이를 상태기준으로 보기편하게 아래와 같이 확인 할 수도 있다.

$ systemctl list-unit-files
UNIT FILE                                   STATE
sys-fs-fuse-connections.mount               static
sys-kernel-config.mount                     static
sys-kernel-debug.mount                      static
tmp.mount                                   disabled
brandbot.path                               disabled
systemd-ask-password-console.path           static
systemd-ask-password-plymouth.path          static
systemd-ask-password-wall.path              static
session-1.scope                             static
session-2.scope                             static
auditd.service                              enabled
[email protected]                             disabled
avahi-daemon.service                        enabled
... 하략 ...

그 외에 Listening하는 소켓관련 목록을 확인 할 수도 있고 각 서비스를 의존성에 따라 확인 할 수도 있다.

$ systemctl list-sockets
LISTEN                          UNIT                         ACTIVATES
/dev/initctl                    systemd-initctl.socket       systemd-initctl.service
/dev/log                        systemd-journald.socket      systemd-journald.service
/run/dmeventd-client            dm-event.socket              dm-event.service
/run/dmeventd-server            dm-event.socket              dm-event.service
/run/lvm/lvmetad.socket         lvm2-lvmetad.socket          lvm2-lvmetad.service
/run/systemd/journal/socket     systemd-journald.socket      systemd-journald.service
/run/systemd/journal/stdout     systemd-journald.socket      systemd-journald.service
/run/systemd/shutdownd          systemd-shutdownd.socket     systemd-shutdownd.service
/run/udev/control               systemd-udevd-control.socket systemd-udevd.service
/var/run/avahi-daemon/socket    avahi-daemon.socket          avahi-daemon.service
/var/run/dbus/system_bus_socket dbus.socket                  dbus.service
kobject-uevent 1                systemd-udevd-kernel.socket  systemd-udevd.service

12 sockets listed.

list-dependencies의 경우 뒤에 Service/Target 이름을 별도로 지정 가능하다.

$ systemctl list-dependencies swap.target
swap.target
├─dev-disk-by\x2did-dm\x2dname\x2drhel\x2dswap.swap
├─dev-disk-by\x2did-dm\x2duuid\x2dLVM\x2dWu6fS25DomohkfmsDzYY8SzAfEPmpojKfhfiV6D6AYa86f2Bb7nOkSq...
├─dev-disk-by\x2duuid-c3591b93\x2d0cc0\x2d457c\x2db1f5\x2d79ea0658d54d.swap
├─dev-dm\x2d1.swap
├─dev-mapper-rhel\x2dswap.swap
├─dev-mapper-rhel\x2dswap.swap
└─dev-rhel-swap.swap

실패 서비스 확인

부팅하는 과정에서 실패한 서비스에 대해서 아래와 같이 확인이 가능하다. 또한, 위에서 언급되었던 list-sockets 같은 명령에도 옵션으로 지정하여 실패한 항목을 확인 할 수 있다.

$ systemctl --failed
systemctl --failed
UNIT          LOAD   ACTIVE SUB    DESCRIPTION
rhnsd.service loaded failed failed LSB: Starts the Spacewalk Daemon

LOAD   = Reflects whether the unit definition was properly loaded.
ACTIVE = The high-level unit activation state, i.e. generalization of SUB.
SUB    = The low-level unit activation state, values depend on unit type.

1 loaded units listed. Pass --all to see loaded but inactive units, too.
To show all installed unit files use 'systemctl list-unit-files'.

6. 서비스 관리

서비스를 설정하고 관리하는 방법은 기본적으로 아래와 같다.

- 서비스 활성화
$ systemctl enable [서비스명]

- 서비스 비활성화
$ systemctl disable [서비스명]

- 서비스 시작
$ systemctl start [서비스명]

- 서비스 종료
$ systemctl stop [서비스명]

- 서비스 재시작
$ systemctl restart [서비스명]

- 서비스 갱신
$ systemctl reload [서비스명]

그리고, 각각의 서비스에 대해서 부팅 때 실행되도록 설정 되었는지 여부 및 현재 실행 여부 등을 확인 할 수 있으며 간단한 응답으로 종료하기 때문에 스크립트 작성할 때 좋다.

$ systemctl is-enabled [서비스명]
$ systemctl is-active [서비스명]

예시)
$ systemctl is-enabled crond
enabled
$ systemctl is-active auditd
active

이렇게 변경한 서비스 설정 정보를 데몬에 반영하기 위해서는 아래와 같이 실행하면 된다.

$ systemctl daemon-reload

서비스 상태도 확인이 가능한데 이 상태 확인은 기존 init 스크립트가 제공하던 실행 여부 이상으로 각 서비스의 CGroup 관련 정보 및 로그정보까지 확인이 가능하다. (-ㅣ 옵션은 한 줄을 넘어서는 라인을 축약하지 말고 전부 보여주라는 옵션이다)

$ systemctl status crond -l
crond.service - Command Scheduler
   Loaded: loaded (/usr/lib/systemd/system/crond.service; enabled)
   Active: active (running) since Tue 2014-10-21 00:31:58 EDT; 2h 10min ago
 Main PID: 583 (crond)
   CGroup: /system.slice/crond.service
           └─583 /usr/sbin/crond -n

Oct 21 00:31:58 localhost.localdomain systemd[1]: Started Command Scheduler.
Oct 21 00:31:58 localhost.localdomain crond[583]: (CRON) INFO (RANDOM_DELAY will be scaled with factor 2% if used.)
Oct 21 00:31:59 localhost.localdomain crond[583]: (CRON) INFO (running with inotify support)

상태확인 뿐만 아니라 kill 명령을 통해서 서비스 관련 모든 프로세스에 kill 시그널을 보낼 수도 있다.

$ systemctl kill httpd

웹서버(http) 관련 프로세스가 모두 죽어있음을 확인할 수 있다.

7. 로그 관리

systemd는 단순한 init 대체제가 아니라 시스템 전반적인 부분을 관리하는 프로그램이기 때문에 로그에 대한 관리 부분도 있다. 로그는 systemd-journald를 통해서 관리되며 이를 제어하는 툴은 journalctl이다.

단순히 전체 이벤트 로그를 확인하기 위해서는 journalctl 만 실행하면 되며 몇 가지 유용한 커맨드를 소개한다.

바이너리에 대한 이벤트

프로세스로 실행이 가능한 특정 바이너리에 대한 이벤트는 아래와 같이 확인 할 수 있다.

$ journalctl /sbin/crond
-- Logs begin at Tue 2014-10-21 00:31:54 EDT, end at Tue 2014-10-21 04:01:01 EDT. --
Oct 21 00:31:58 localhost.localdomain crond[583]: (CRON) INFO (RANDOM_DELAY will be scaled with fac
Oct 21 00:31:59 localhost.localdomain crond[583]: (CRON) INFO (running with inotify support)
lines 1-3/3 (END)

기간 조회

특정 일자부터의 이벤트 로그를 확인하는 방법은 아래와 같은데

$ journalctl --since=today

today 대신에 yesterday, tomorrow 같은 단어도 가능하다. 또한, “YYYY-MM-DD HH:MM:SS” 형태의 시간 값을 이용해서 구간 별 조회가 가능한데 시간을 생략하면 0시0분0초를 기준으로 하게 된다.

$ journalctl --since=2014-10-21 --until=2014-10-22
-- Logs begin at Tue 2014-10-21 00:31:54 EDT, end at Tue 2014-10-21 04:01:01 EDT. --
Oct 21 00:31:54 localhost.localdomain systemd-journal[81]: Runtime journal is using 8.0M (max 92.0M
Oct 21 00:31:54 localhost.localdomain systemd-journal[81]: Runtime journal is using 8.0M (max 92.0M
Oct 21 00:31:54 localhost.localdomain kernel: Initializing cgroup subsys cpuset

마지막 부팅 이후의 로그는 -b 옵션으로 확인이 가능하며

$ journalctl -b

속성에 따른 조회

특정, 우선순위에 따른 (syslog에서 지정하는 debug, info, err 등) 조회도 가능하다.

$ journalctl -p err
-- Logs begin at Tue 2014-10-21 00:31:54 EDT, end at Tue 2014-10-21 04:01:01 EDT. --
Oct 21 00:31:54 localhost.localdomain kernel: Detected CPU family 6 model 69
Oct 21 00:31:54 localhost.localdomain kernel: Warning: Intel CPU model - this hardware has not unde
Oct 21 00:31:54 localhost.localdomain kernel: tsc: Fast TSC calibration failed
Oct 21 00:31:54 localhost.localdomain systemd-fsck[260]: fsck failed with error code 8.
Oct 21 00:31:56 localhost.localdomain kernel: piix4_smbus 0000:00:07.0: SMBus base address uninitia
Oct 21 00:31:58 localhost.localdomain audispd[544]: No plugins found, exiting
Oct 21 00:32:00 localhost.localdomain systemd[1]: Failed to start LSB: Starts the Spacewalk Daemon.
lines 1-8/8 (END)

기타 옵션들

이 외에 tail -f로 로그파일을 걸어둔 것 같은 효과를 갖는 -f 옵션과 json을 비롯한 다양한 포맷으로 로그를 재포매팅 하는 옵션인 -o 옵션 등이 있다.

$ journalctl -f
$ journalctl -p err -o json-pretty
{
        "__CURSOR" : "s=ee396e27b84346d4a5163e52bb6a839c;i=5b;b=03fa23106cc04ce99a97bf6a5e45e6aa;m=
        "__REALTIME_TIMESTAMP" : "1413865914539904",
        "__MONOTONIC_TIMESTAMP" : "459391",
        "_BOOT_ID" : "03fa23106cc04ce99a97bf6a5e45e6aa",
        "_MACHINE_ID" : "a73fc4e71dd64fe98f580bffe567ea29",
        "_HOSTNAME" : "localhost.localdomain",
        "_SOURCE_MONOTONIC_TIMESTAMP" : "0",
        "_TRANSPORT" : "kernel",
        "SYSLOG_IDENTIFIER" : "kernel",
        "PRIORITY" : "2",
        "MESSAGE" : "Detected CPU family 6 model 69"
}

8. CGroup 관리

리소스 정보 조회

systemd에는 CGroup(control group)을 관리하는 기능도 포함되어있다. (홈페이지에서 문서를 읽다보면 없는게 있을까 싶을 정도로 너무 많은 기능을 가지고 있다. 문서 서두에 이야기 한 것 처럼 이로 인해 Unix 철학과 대치 된다고 논란이 있던 프로그램이다)

먼저 systemd-cgls 명령은 현재 cgroup에 대한 정보를 타입별로 출력해준다.

$ systemd-cgls
├─1 /usr/lib/systemd/systemd --switched-root --system --deserialize 24
├─user.slice
│ ├─user-1000.slice
│ │ └─session-2.scope
│ │   ├─10191 sshd: lunatine [priv
│ │   ├─10195 sshd: lunatine@pts/0
│ │   └─17794 systemd-cgls
│ └─user-0.slice
│   └─session-1.scope
│     ├─ 595 login -- root
│     └─7805 -bash
└─system.slice
  ├─httpd.service
  │ ├─17779 /usr/sbin/httpd -DFOREGROUND
  │ ├─17780 /usr/sbin/httpd -DFOREGROUND
  │ ├─17781 /usr/sbin/httpd -DFOREGROUND
  │ ├─17782 /usr/sbin/httpd -DFOREGROUND
  │ ├─17783 /usr/sbin/httpd -DFOREGROUND
  │ └─17784 /usr/sbin/httpd -DFOREGROUND
... 하략 ...

systemd-cgtop의 경우는 흔히 알고 있는 top 명령과 같이 cgroup에 대하여 CPU, Memory, I/O에 대한 정렬 결과를 출력해 준다. 본인이 설정한 cgroup에 대해 적합하게 리소스가 분배되고 있는지 확인하는데 유용하다.

리소스 관리

앞서 살펴보았던 systemctl의 경우 set-property 명령을 통해서 리소스 값을 제어할 수 있는데 실제로는 systemctl로 설정하면 systemd.resource-control이라는 프로그램이 해당 리소스 할당작업을 수행한다.

$ systemctl set-property httpd.service CPUShares=200
$ systemctl show -p CPUShares httpd.service
CPUShares=200
$ cat /sys/fs/cgroup/cpu/system.slice/httpd.service/cpu.shares
200

systemctl을 통해서 설정을 하지만 실제로 systemd.resource-control에 의해서 설정되기 때문에 상세한 설정 옵션에 대해서는 systemd.resource-control의 man 페이지를 확인해야 한다.

이러한 리소스관리 툴도 포함되어있기 때문에 cgroup 설정을 위한 스크립트 작성이 한결 간편해지고 체계적이 될 수 있다.

9. 호스트명 설정

systemd에는 hostnamectl이란 툴도 있는데 이 툴로 호스트명 설정도 가능하다.

$ hostnamectl
   Static hostname: rhel7
         Icon name: computer
           Chassis: n/a
        Machine ID: a73fc4e71dd64fe98f580bffe567ea29
           Boot ID: 711ec89043a543fa8751aa686257dd81
    Virtualization: oracle
  Operating System: Red Hat Enterprise Linux Server 7.0 (Maipo)
       CPE OS Name: cpe:/o:redhat:enterprise_linux:7.0:GA:server
            Kernel: Linux 3.10.0-123.el7.x86_64
      Architecture: x86_64

$ hostnamectl set-hostname new-rhel7

위 명령은 /etc/hostname 설정파일을 변경하게 된다. 옵션 중에 --transient는 DHCP, mDNS에 의해서 변경가능한 커널에 의해 관리되는 호스트명을 수정하고 --pretty는 UTF-8 인코딩으로 호스트명을 지정한다.

10. 로케일 설정

systemd에 포함된 localectl은 시스템의 로케일을 설정한다. 현재 설정된 정보는 localectl 실행 결과로 확인 할 수 있다.

$ localectl
   System Locale: LANG=en_US.UTF-8
       VC Keymap: us
      X11 Layout: us

로케일 변경은 set-locale로 키맵은 set-keymap, X서버를위한 키맵은 set-x11-keymap으로 변경 가능하다. 이 툴은 /usr/lib/locale/locale-archive 정보를 바탕으로 /var/run/dbus/system_bus_socket을 통해서 변경을 수행한다.

$ localectl set-locale LANG=ko_KR.UTF-8
$ localectl set-keymap fr
$ localectl set-x11-keymap fr
$ localectl
   System Locale: LANG=ko_KR.UTF-8
       VC Keymap: fr
      X11 Layout: fr

변경가능한 로케일, 키맵 등은 list-locales, list-keymaps, list-x11-keymap-models, list-x11-keymap-layouts, list-x11-keymap-variants, list-x11-keymap-options으로 확인 가능하다.

11. 사용자 관리

loginctl 툴을 이용해서 현재 사용자 세션에 대해서 관리가 가능하다.

$ loginctl list-users
       UID USER
      1000 lunatine

1 users listed.

list-session으로 현재 세션들을 확인 할 수 있으며 lock-session 등의 명령으로 세션을 잠글 수 있다. 또한, show-user를 통해 사용자 정보 조회도 가능하고 kill-user를 통해서 사용자 프로세스에 시그널을 보낼 수도 있다.

자세한 내용은 loginctl --help로 확인해 보도록 하자.

12. 시간 설정

timedatectl은 시간대를 조회하고 설정하는 기능을 제공한다. set-time은 시간을 set-timezone은 시간대를 설정한다. 또한, set-ntp를 통해 NTP 활성화 여부도 설정이 가능하다.

$ timedatectl
      Local time: Tue 2014-10-21 09:45:29 EDT
  Universal time: Tue 2014-10-21 13:45:29 UTC
        RTC time: Tue 2014-10-21 13:45:29
        Timezone: America/New_York (EDT, -0400)
     NTP enabled: n/a
NTP synchronized: no
 RTC in local TZ: no
      DST active: yes
 Last DST change: DST began at
                  Sun 2014-03-09 01:59:59 EST
                  Sun 2014-03-09 03:00:00 EDT
 Next DST change: DST ends (the clock jumps one hour backwards) at
                  Sun 2014-11-02 01:59:59 EDT
                  Sun 2014-11-02 01:00:00 EST

$ timedatectl set-timezone Asia/Seoul
$ timedatectl
      Local time: Tue 2014-10-21 22:46:04 KST
  Universal time: Tue 2014-10-21 13:46:04 UTC
        RTC time: Tue 2014-10-21 13:46:04
        Timezone: Asia/Seoul (KST, +0900)
     NTP enabled: n/a
NTP synchronized: no
 RTC in local TZ: no
      DST active: n/a

13. 원격 관리

systemd의 모든 명령어들은 -H옵션을 제공하는데 이 옵션을 통해서 원격지 서버에 ssh 접속을 통해 설정 및 정보조회가 가능하다. 아래 예제는 원격지 서버의 호스트명을 수정하는 내용이다.

$ hostnamectl -H [email protected] set-hostname rhel7-new
[email protected]'s password:

$ hostnamectl -H [email protected]
root@rhel7's password:
   Static hostname: rhel7-new
         Icon name: computer
           Chassis: n/a
        Machine ID: a73fc4e71dd64fe98f580bffe567ea29
           Boot ID: 711ec89043a543fa8751aa686257dd81
    Virtualization: oracle
  Operating System: Red Hat Enterprise Linux Server 7.0 (Maipo)
       CPE OS Name: cpe:/o:redhat:enterprise_linux:7.0:GA:server
            Kernel: Linux 3.10.0-123.el7.x86_64
      Architecture: x86_64

마치며

본 문서에서 systemd의 기능들을 간단히 알아보았다. systemd는 마치 맥가이버칼 처럼 다양한 시스템 설정 기능을 포함하고 있으며 계속해서 개선되고 추가되고 있다. Unix의 ‘한 가지만 잘하자’ 철학에 위배되는 프로그램일지 모르지만 실제는 내부적으로 한가지 일을 잘 하는 툴 들로 구성되어있기 때문에 위배라고 보기도 어렵다.

그리고 systemd는 기존 System V Init에 익숙한 사용자를 위해 디렉토리 구조 및 호환성을 일부 제공하고 있다. 심볼릭 링크이지만 init도 존재하고 0123456qQuUsS 옵션도 제공한다. 이 때문에 runlevel$.target 심볼릭링크 파일이 존재한다.

대표적인 리눅스 배포판에서 선택되었으며 이제는 엔터프라이즈 리눅스 배포판에도 선택이 되었다. 머지 않아 Debian 계열 배포판에서도 기본으로 채택이 될 것이라고 하니 이제 슬슬 System V Init을 떠나 보낼 때가 왔나보다.

기존 시스템 관리에 사용하던 자동화 스크립트를 systemd에 맞춰서 수정 할 일만 남았다.

https://lunatine.net/2014/10/21/about-systemd/

 

 

320x100
반응형