Red Hat Certified Engineer (RHCE) #14 RHCSA 자동화 1: 사용자/그룹, 패키지/repository

8 분 소요

#13 system roles까지 Ansible의 문법과 구조화 도구를 모두 익혔습니다. 이제부터 네 편은 그 도구를 무기로 RHCSA의 손작업을 플레이북으로 자동화하는 종합 구간입니다. RHCE 시험 비중의 절반가량이 이 “RHCSA 작업을 Ansible로 자동화"이므로, 여기서부터가 시험 비중의 핵심 구간입니다.

이번 글에서는 RHCSA에서 useradd,groupadd,dnf install로 손수 하던 일을, user,group,dnf,yum_repository 모듈로 바꿔 멱등성 있게 처리하겠습니다. 손작업이 궁금하면 RHCSA #8 사용자와 그룹, RHCSA #11 패키지와 repository를 먼저 확인하면 자동화할 대상이 또렷해집니다.

group 모듈: 그룹부터 만든다 #

사용자를 그룹에 넣으려면 그룹이 먼저 있어야 합니다. RHCSA에서 groupadd developers로 하던 일을 ansible.builtin.group 모듈로 처리합니다.

developers 그룹 생성
- name: developers 그룹 보장
  ansible.builtin.group:
    name: developers
    gid: 5000
    state: present

주요 옵션은 단순합니다.

옵션의미
name그룹 이름(필수)
gid지정할 GID. 생략하면 시스템이 자동 할당
statepresent(생성,유지) 또는 absent(삭제)
systemtrue면 시스템 그룹으로 생성

state: present는 그룹이 없으면 만들고 이미 있으면 그대로 둡니다. 두 번 돌려도 두 번째에는 changed가 0이 되는 멱등성이 모듈의 기본 동작입니다.

user 모듈: 사용자 생성의 중심 #

ansible.builtin.user 모듈은 RHCE에서 가장 자주 쓰는 모듈 중 하나입니다. RHCSA의 useradd,usermod를 한 모듈로 묶습니다.

user 모듈로 계정 생성
- name: 사용자 생성
  hosts: all
  become: true
  tasks:
    - name: 개발자 계정 생성
      ansible.builtin.user:
        name: jdoe
        uid: 5001
        comment: "John Doe"
        group: developers
        groups: wheel
        append: true
        shell: /bin/bash
        state: present

핵심 옵션을 정리하겠습니다.

옵션의미
name사용자 이름(필수)
uid지정할 UID
group기본 그룹(primary group)
groups보조 그룹(supplementary). 쉼표 목록 또는 리스트
appendtrue면 groups를 기존에 추가. false(기본)면 덮어씀
password암호화된 비밀번호 해시(평문 아님)
shell로그인 셸. 예로 /bin/bash, /sbin/nologin
statepresent 또는 absent
removestate: absent와 함께 true면 홈 디렉터리까지 삭제

append의 함정 #

groups만 주고 append를 빠뜨리면, 기본값이 false이므로 기존 보조 그룹을 전부 덮어쓰고 지정한 그룹만 남깁니다. “사용자를 wheel 그룹에 추가하라"는 문제에서 append: true를 빠뜨리면 다른 그룹이 사라져 감점됩니다. 보조 그룹을 더하는 의도라면 append: true를 반드시 함께 적겠습니다.

password: 비밀번호는 해시로 다룬다 #

user 모듈의 password는 평문이 아니라 암호화된 해시를 받습니다. 평문을 그대로 넣으면 그 문자열 자체가 해시로 저장되어 로그인이 되지 않습니다. 해시는 password_hash 필터로 만듭니다.

password_hash로 비밀번호 설정
- name: 비밀번호와 함께 사용자 생성
  hosts: all
  become: true
  vars:
    user_password: "{{ vault_user_password }}"
  tasks:
    - name: jdoe 생성과 비밀번호 설정
      ansible.builtin.user:
        name: jdoe
        password: "{{ user_password | password_hash('sha512') }}"
        shell: /bin/bash
        state: present

여기서 평문 비밀번호 vault_user_password는 #10 Ansible Vault에서 다룬 대로 Vault로 암호화한 변수 파일(ansible-vault create group_vars/all/vault.yml)에 두는 것이 정석입니다. 평문 비밀번호를 플레이북에 그대로 적으면 시험에서 감점 대상입니다. 실행 시에는 --vault-password-file ~/.vault_pass로 잠금을 풉니다.

password_hash('sha512')는 호출할 때마다 salt가 달라져 해시가 바뀌므로, 그대로 두면 매 실행이 changed로 보일 수 있습니다. 멱등성을 엄격히 맞춰야 한다면 고정 salt를 주는 방식도 있지만, 시험에서는 비밀번호가 올바로 설정되어 로그인이 되는지가 채점의 핵심입니다.

loop로 사용자 다수 생성: 시험 단골 #

RHCE에서 가장 자주 나오는 형태는 사용자 목록을 변수로 받아 loop로 한 번에 생성하는 패턴입니다. #9 loop에서 익힌 loop를 그대로 활용합니다. 사용자 목록을 변수 파일에 둡니다.

group_vars/all/users.yml
# group_vars/all/users.yml
users:
  - { name: alice, groups: developers }
  - { name: bob,   groups: developers }
  - { name: carol, groups: ops }

그 위에 사용자를 loop로 생성합니다.

loop로 사용자 일괄 생성
- name: 사용자 일괄 생성
  ansible.builtin.user:
    name: "{{ item.name }}"
    groups: "{{ item.groups }}"
    append: true
    password: "{{ default_password | password_hash('sha512') }}"
    shell: /bin/bash
    state: present
  loop: "{{ users }}"

loop: "{{ users }}"는 리스트의 각 원소를 item으로 받아, item.name,item.groups로 dictionary 필드에 접근합니다. 사용자가 수십 명이라도 변수 목록만 늘리면 됩니다. 조건을 더하고 싶으면 when: item.state | default('present') == 'present'처럼 필드별로 분기할 수도 있습니다. 아래 통합 playbook에서 이 패턴을 그대로 활용합니다.

dnf 모듈: 패키지 관리 #

패키지 설치,삭제는 ansible.builtin.dnf 모듈로 처리합니다. RHCSA의 dnf install,dnf remove에 대응합니다.

dnf 모듈로 패키지 관리
- name: 패키지 관리
  hosts: all
  become: true
  tasks:
    - name: 여러 패키지 설치
      ansible.builtin.dnf:
        name:
          - httpd
          - mariadb-server
          - vim-enhanced
        state: present

    - name: 패키지 최신 상태로
      ansible.builtin.dnf:
        name: tmux
        state: latest

    - name: 패키지 제거
      ansible.builtin.dnf:
        name: telnet
        state: absent

주요 옵션은 다음과 같습니다.

옵션의미
name패키지 이름. 리스트로 여러 개 한 번에
statepresent(설치), latest(최신으로 갱신), absent(제거)
enablerepo특정 repository만 활성화해 설치
disablerepo특정 repository를 비활성화

state: present는 없으면 설치하고 있으면 그대로 두므로 멱등성이 보장됩니다. 반면 state: latest는 새 버전이 있으면 매번 갱신하므로, “특정 패키지를 설치만 하라"는 문제에는 present를 쓰는 편이 의도에 맞습니다.

패키지 그룹 설치 #

여러 패키지를 묶은 그룹은 @ 접두사로 설치합니다. RHCSA에서 dnf group install 하던 일입니다.

패키지 그룹 설치
- name: 개발 도구 그룹 설치
  ansible.builtin.dnf:
    name: "@Development Tools"
    state: present

module stream #

AppStream의 module stream은 @모듈:스트림 형식으로 지정합니다. 예로 nginx의 1.22 스트림을 설치하겠습니다.

module stream 설치
- name: nginx 1.22 module stream 설치
  ansible.builtin.dnf:
    name: "@nginx:1.22"
    state: present

@nginx:1.22는 nginx 모듈의 1.22 스트림을 활성화하고 기본 프로파일을 설치합니다. 특정 버전 스트림을 고정해야 하는 문제에서 이 표기를 그대로 활용합니다.

yum_repository 모듈: repository 추가 #

인터넷이나 내부 서버의 repository를 등록하려면 ansible.builtin.yum_repository 모듈을 씁니다. RHCSA에서 /etc/yum.repos.d/에 .repo 파일을 손으로 만들던 일을 자동화합니다.

yum_repository로 repo 등록
- name: 사내 repository 등록
  hosts: all
  become: true
  tasks:
    - name: BaseOS repo 추가
      ansible.builtin.yum_repository:
        name: internal-baseos
        description: "Internal BaseOS Repository"
        baseurl: http://repo.example.com/baseos
        gpgcheck: true
        gpgkey: http://repo.example.com/RPM-GPG-KEY-internal
        enabled: true
        state: present

주요 옵션을 정리하겠습니다.

옵션의미
namerepo ID. .repo 파일 안 섹션 이름이 됨
description사람이 읽는 설명. .repo의 name= 항목
baseurl패키지를 받아올 기준 URL
gpgchecktrue면 GPG 서명 검증
gpgkey검증에 쓸 GPG 키 위치
enabled이 repo의 활성화 여부
file저장할 .repo 파일 이름(생략하면 name 사용)

name은 .repo 파일의 섹션 ID가 되고, description은 그 안 name= 항목으로 들어갑니다. 두 필드가 헷갈리기 쉬우니 표로 짝을 외워 두겠습니다. gpgcheck: true로 두면 gpgkey도 함께 지정해야 설치 시 검증을 통과합니다.

시험 단골: 통합 playbook #

실제 시험에서는 위 모듈이 한 플레이북에 모입니다. “repository를 추가하고, 그 repo에서 패키지를 설치하고, 사용자 목록을 loop로 만들라"는 형태가 전형입니다. 한 파일로 묶어 보겠습니다.

통합 플레이북
- name: RHCSA 자동화 통합
  hosts: webservers
  become: true
  vars_files:
    - group_vars/all/users.yml
    - group_vars/all/vault.yml
  tasks:
    - name: 사내 repository 등록
      ansible.builtin.yum_repository:
        name: internal-appstream
        description: "Internal AppStream"
        baseurl: http://repo.example.com/appstream
        gpgcheck: false
        enabled: true

    - name: 웹 패키지 설치
      ansible.builtin.dnf:
        name:
          - httpd
          - "@nginx:1.22"
        state: present
        enablerepo: internal-appstream

    - name: 운영 그룹 생성
      ansible.builtin.group:
        name: ops
        state: present

    - name: 사용자 일괄 생성
      ansible.builtin.user:
        name: "{{ item.name }}"
        groups: "{{ item.groups }}"
        append: true
        password: "{{ vault_default_password | password_hash('sha512') }}"
        shell: /bin/bash
        state: present
      loop: "{{ users }}"

이 한 플레이북에 이번 글의 모듈이 모두 들어 있습니다. repository를 등록하고, enablerepo로 그 repo에서 패키지를 설치하며, 그룹을 먼저 만든 뒤 사용자를 loop로 생성하는 순서가 자연스럽습니다.

시험 포인트 #

  • password는 해시만 받는다. 평문을 넣으면 로그인이 안 됩니다. password_hash('sha512') 필터로 해시를 만들고, 평문 비밀번호는 Vault로 암호화한 변수에 두겠습니다.
  • append: true를 잊지 않는다. 보조 그룹을 더하라는 문제에서 append를 빠뜨리면 기존 보조 그룹이 전부 사라집니다. 더하는 의도면 항상 함께 적습니다.
  • yum_repository의 name과 description을 구분한다. name은 repo ID, description은 .repo의 name= 설명입니다. 짝을 헷갈리면 검증이 어긋납니다.
  • module stream은 @모듈:스트림 표기. 예로 @nginx:1.22처럼 적습니다.
  • state: present와 latest를 구분한다. “설치만"이면 present, “최신으로"면 latest입니다.
  • 그룹을 사용자보다 먼저 만든다. 사용자의 보조 그룹이 없으면 작업이 실패합니다. 한 플레이북 안에서 group 작업을 user 작업 위에 둡니다.

정리 #

이번 글에서 자동화한 것:

  • group 모듈. name,gid,state로 그룹을 멱등하게 생성
  • user 모듈. name,uid,group,groups,append,shell,state. 비밀번호는 password_hash+Vault
  • dnf 모듈. name,state(present/latest/absent),enablerepo. 패키지 그룹은 @그룹, module stream은 @모듈:스트림
  • yum_repository 모듈. name,description,baseurl,gpgcheck,gpgkey,enabled로 repo 등록
  • loop 패턴. 사용자 목록 변수를 loop로 일괄 생성하는 시험 단골
  • 통합 playbook. repo 등록 → 패키지 설치 → 그룹,사용자 생성을 한 파일로

다음: RHCSA 자동화 2 #

사용자,그룹과 패키지,repository를 플레이북으로 옮겼습니다. 이제 그 위에서 도는 서비스를 자동화할 차례입니다.

#15 RHCSA 자동화 2: 서비스, chronyd, log에서는 service,systemd 모듈로 서비스를 시작,활성화하는 법, chronyd 시간 동기화를 plays로 구성하는 법, 그리고 로그 관련 설정까지 손작업을 모듈로 바꿔 정리하겠습니다.

X