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). 쉼표 목록 또는 리스트
appendtruegroups를 기존에 추가. 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사람이 읽는 설명. .reponame= 항목
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_repositorynamedescription을 구분한다. name은 repo ID, description.reponame= 설명입니다. 짝을 헷갈리면 검증이 어긋납니다.
  • module stream은 @모듈:스트림 표기. 예로 @nginx:1.22처럼 적습니다.
  • state: presentlatest를 구분한다. “설치만"이면 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