소개
Courtyard Sessions는 두 서버에서 정상적인 웹사이트를 운영하고 있습니다. 새 프로그램은 준비되었지만 두 노드를 동시에 업데이트하면 방문자가 사이트를 이용하지 못할 수 있습니다. Ansible로 상태를 확인하고 한 번에 서버 하나씩 새 패키지를 게시해 보세요.
기본적인 터미널 경로 이동과 작은 Ansible 인벤토리, 변수, file/copy 작업을 사용한 경험이 필요합니다. 게이트웨이, SSH 접속, 패키지는 준비되어 있습니다. 이 실습에서는 실행 위치, 위임, 배치, 릴리스 링크, 전체 HTTP 검사를 사용하기 전에 설명합니다. 소요 시간은 읽기와 일반적인 오류 수정을 포함한 저자의 예상치입니다.
각 작업의 실행 위치 확인하기
이 단계에서는 각 서버의 활성 릴리스와 실제 라우팅 풀을 보여 주는 읽기 전용 확인 playbook을 만듭니다.
Courtyard Sessions에는 이미 방문자용 사이트가 운영되고 있습니다. Website 탭을 여세요. **Good things, around the corner.**를 읽고 The program을 누르세요. 페이지가 Coffee in the courtyard로 시작하는 두 세션으로 이동합니다. **Download the program (CSV)**을 누르고 다운로드한 파일을 여세요. 헤더 time,session,place 다음에 화면에 나온 두 세션이 있습니다. CSV는 필드를 쉼표로 구분한 텍스트 표입니다. 업데이트 중에도 이 실제 다운로드를 이용할 수 있어야 합니다.
게이트웨이는 짧은 HTTP 요청을 두 서버로 분배합니다. 활성 풀에서 서버를 빼면 게이트웨이가 그 서버로 새 요청을 보내지 않으며, 다시 넣으면 순환 대상이 됩니다. 준비된 작은 게이트웨이는 장시간 연결을 드레이닝하거나 완전한 클라우드 게시 플랫폼을 재현하지 않습니다. 두 서버는 이 교육용 VM의 커널을 공유하며 SSH로 관리하는 컨테이너입니다. 컨트롤러는 Ansible과 게이트웨이가 실행되는 머신입니다.
Controller 탭으로 전환하세요. 시작 경로는 /home/labex/project입니다. pwd는 현재 디렉터리를 표시하고 cat은 파일을 출력합니다.
pwd
cat inventory.yml
cat group_vars/all.yml
인벤토리는 YAML과 호환되는 구조화된 데이터입니다. website 그룹에는 실제 ansible_host 주소와 준비된 SSH 설정을 가진 web1, web2가 있습니다. control 그룹에는 로컬 연결을 사용하는 controller가 있습니다. 프로젝트의 ansible.cfg가 선택한 inventory.yml을 사용하세요. 이미지의 초기 inventory.ini는 이 과정의 인벤토리가 아닙니다.
그룹 변수에는 backend_port(8090), 컨트롤러 전용 pool_api, 허용된 release_root, 로컬 package_root, 릴리스 식별자가 있습니다. baseline_release는 courtyard-preview, approved_release는 courtyard-weekend입니다. 이는 Ansible이나 소프트웨어 버전이 아니라 디렉터리 이름입니다. HTTP 포트는 특정 주소에서 요청을 받는 서비스를 구분합니다. /identity는 실제 실행 중인 릴리스를 식별하는 구조화된 텍스트인 JSON을 반환합니다. /pool은 컨트롤러의 현재 활성 노드 목록을 반환합니다.
명령에서 “No such file or directory”가 나오면 ls로 철자를 확인하고 필요하면 cd /home/labex/project로 돌아오세요. 읽기는 안전하게 반복할 수 있습니다. 모든 패키지 파일, 연결 정보, 원래 릴리스 파일, 주최자의 비공개 메모를 보존하세요.
Ansible은 기본적으로 선택한 각 서버에서 SSH를 통해 작업을 실행합니다. 그곳의 127.0.0.1은 해당 서버 자신입니다. delegate_to: controller는 그 특정 작업을 컨트롤러에서 실행하며, 컨트롤러 자체의 127.0.0.1에는 풀 API가 있습니다. 위임해도 inventory_hostname은 바뀌지 않고 현재 관리 중인 서버를 가리킵니다. 따라서 컨트롤러의 요청이 올바른 서버를 대상으로 삼을 수 있습니다.
inspect.yml을 만드세요. cat > filename은 파일을 쓰며 같은 이름의 기존 파일을 덮어씁니다. <<'YAML'은 here-document를 시작합니다. 마지막 YAML을 별도 줄에 입력할 때까지 내용을 붙여 넣으세요. 따옴표로 감싼 구분자는 붙여 넣은 텍스트를 그대로 유지하고, Ansible이 playbook 실행 시 표현식을 처리합니다. 들여쓰기는 공백을 사용합니다. {{ variable }}은 Ansible 변수를 삽입합니다. gather_facts: false는 이 연습과 관계없는 머신 정보 수집을 생략합니다.
uri 모듈은 실제 HTTP 요청을 보냅니다. 기본 메서드 GET은 리소스를 읽습니다. register는 실제 결과를 지정한 이름으로 저장합니다. JSON 응답에서는 .json에 분석된 필드가 들어 있습니다. debug는 저장된 관찰 결과를 출력하며 릴리스나 풀 상태를 만들지 않습니다.
cat > inspect.yml <<'YAML'
- name: Inspect the running website without changing it
hosts: website
gather_facts: false
tasks:
- name: Read this backend on its own loopback address
ansible.builtin.uri:
url: "http://127.0.0.1:{{ backend_port }}/identity"
register: backend
- name: Read the pool on the controller
ansible.builtin.uri:
url: "{{ pool_api }}/pool"
delegate_to: controller
register: pool
- name: Show the actual release and active pool
ansible.builtin.debug:
msg:
node: "{{ inventory_hostname }}"
release: "{{ backend.json.release }}"
active: "{{ pool.json.active }}"
YAML
playbook을 실행하세요. ansible-playbook은 작업을 읽고 인벤토리에 적용합니다.
ansible-playbook inspect.yml
각 서버는 release: courtyard-preview를 보여야 하며 active에는 web1과 web2가 있어야 합니다. 출력 형식과 주소는 다를 수 있습니다. 첫 번째 uri는 서버에서 실행되는 반면 풀 읽기에는 web1 -> controller 같은 위임 표시가 나옵니다. 두 작업은 모두 읽기 전용입니다.
YAML 들여쓰기가 잘못되었다면 전체 블록을 다시 복사해 파일을 고치고 재시도하세요. 이 playbook은 파일을 복사하거나 풀을 변경하면 안 됩니다. 이제 게시 후 상태를 다시 관찰할 유용한 도구가 생겼습니다.
한 번에 서버 하나씩 게시하기
이 단계에서는 승인된 웹 패키지를 SSH로 전달하고 방문자에게 제공되는 실제 콘텐츠를 확인한 뒤, 다음 서버를 시작하기 전에 현재 서버를 서비스로 돌려놓습니다.
Controller 탭의 /home/labex/project에 머무르세요. 패키지 목록과 승인된 프로그램을 읽으세요. ls는 디렉터리 항목을 나열합니다.
ls packages
cat packages/courtyard-weekend/schedule.csv
승인된 프로그램에는 Bread and stories, Acoustic afternoon, Print a postcard의 세 세션이 있습니다. 올바른 홈페이지만으로는 충분하지 않습니다. 방문자에게는 그에 맞는 전체 다운로드가 필요합니다. courtyard-incomplete 패키지는 다음 실습용입니다. 여기서는 release_id 변수가 이미 선택한 대로 courtyard-weekend를 사용하세요.
playbook은 serial: 1로 호스트 하나의 배치를 처리합니다. 배치 안에서 노드를 풀에서 빼고, 패키지를 복사하고, 활성 릴리스 링크를 바꾸고, 두 리소스를 검사하고, 노드를 다시 넣습니다. 그 후에만 다음 배치를 시작합니다. order: sorted는 시연을 web1부터 시작하게 합니다. any_errors_fatal: true는 처리되지 않은 실패를 명시적으로 전파해 play를 중단합니다. 이처럼 호스트 하나인 배치에서는 해당 호스트의 실패만으로 배치 전체가 실패합니다. 이 옵션은 완료된 작업을 되돌리지 않습니다. 다음 실습에서 복구를 추가합니다.
풀 API는 /pool로 HTTP POST와 JSON 본문을 받습니다. node는 관리 대상 노드이며 enabled는 제외할 때 불리언 false, 추가할 때 true입니다. body_format: json은 본문을 올바르게 직렬화합니다. 풀 작업은 컨트롤러에서, 파일 작업은 선택한 SSH 서버에서 실행됩니다.
state: directory의 file은 디렉터리를 만듭니다. 모드 '0755'는 소유자에게 쓰기를, 모두에게 읽기와 디렉터리 통과를 허용합니다. copy는 컨트롤러에서 해당 서버로 선택한 패키지를 보냅니다. src 끝의 /는 내용물을 복사하며 읽을 수 있는 파일에 모드 '0644'를 사용합니다. state: link의 file은 심볼릭 링크를 만들거나 갱신합니다. current는 릴리스 디렉터리를 가리킵니다. 준비된 HTTP 서버는 요청할 때마다 링크를 통해 실제 파일을 제공합니다. 링크를 바꾸면 서버를 재시작하지 않고 사이트를 활성화하면서 원래 디렉터리를 보존합니다.
컨트롤러에서 실행되는 HTTP 검사에서 hostvars[inventory_hostname].ansible_host는 관리 대상 서버의 주소를 명시적으로 가져옵니다. 위임 중에는 이러한 참조가 없는 연결 변수가 위임받은 호스트의 값일 수 있습니다. return_content: true는 실제 본문을 .content에 저장합니다.
assert는 that 표현식이 참인지 요구합니다. lookup('file', ...)은 컨트롤러의 패키지에서 예상 파일을 읽으며 ~는 경로 문자열을 연결합니다. rstrip=False는 끝의 줄바꿈을 보존해 잘라낸 일부가 아닌 전체 본문을 비교합니다. 노드를 다시 넣기 전에 후보의 전체 홈페이지와 승인된 전체 CSV가 일치해야 합니다. 404는 서버가 응답했지만 리소스가 없다는 뜻이며, 연결 오류는 서비스에 접속할 수 없다는 뜻입니다. 둘 다 정상 릴리스를 의미하지 않습니다.
전체 배포를 작성하세요. here-document는 확인 도구와 같은 방식을 사용합니다.
cat > rollout.yml <<'YAML'
- name: Release one website backend at a time
hosts: website
order: sorted
gather_facts: false
serial: 1
any_errors_fatal: true
tasks:
- name: Drain this backend from the pool
ansible.builtin.uri:
url: "{{ pool_api }}/pool"
method: POST
body_format: json
body:
node: "{{ inventory_hostname }}"
enabled: false
delegate_to: controller
- name: Create this release directory
ansible.builtin.file:
path: "{{ release_root }}/releases/{{ release_id }}"
state: directory
mode: '0755'
- name: Copy the selected package over SSH
ansible.builtin.copy:
src: "{{ package_root }}/{{ release_id }}/"
dest: "{{ release_root }}/releases/{{ release_id }}/"
mode: '0644'
- name: Activate the selected release
ansible.builtin.file:
src: "{{ release_root }}/releases/{{ release_id }}"
dest: "{{ release_root }}/current"
state: link
- name: Read the real candidate homepage
ansible.builtin.uri:
url: "http://{{ hostvars[inventory_hostname].ansible_host }}:{{ backend_port }}/"
return_content: true
delegate_to: controller
register: homepage
- name: Require the complete candidate homepage
ansible.builtin.assert:
that: homepage.content == lookup('file', package_root ~ '/' ~ release_id ~ '/index.html', rstrip=False)
- name: Read the real candidate download
ansible.builtin.uri:
url: "http://{{ hostvars[inventory_hostname].ansible_host }}:{{ backend_port }}/schedule.csv"
return_content: true
delegate_to: controller
register: download
- name: Require the complete approved program
ansible.builtin.assert:
that: download.content == lookup('file', package_root ~ '/' ~ approved_release ~ '/schedule.csv', rstrip=False)
- name: Rejoin only after both business checks
ansible.builtin.uri:
url: "{{ pool_api }}/pool"
method: POST
body_format: json
body:
node: "{{ inventory_hostname }}"
enabled: true
delegate_to: controller
YAML
먼저 배포하지 않고 YAML과 작업 구조를 검사하세요. --syntax-check는 문법을 검증하지만 기능 결과를 증명하지 않습니다.
ansible-playbook rollout.yml --syntax-check
철자나 들여쓰기 오류가 있으면 전체 파일을 다시 복사하고 검사하세요. 그다음 실제 배포를 실행하세요.
ansible-playbook rollout.yml
web1 작업이 web2보다 먼저 끝나야 합니다. 두 호스트 모두 failed=0, unreachable=0으로 끝나야 합니다. 제외 및 추가 운영 요청은 ok로 표시될 수 있습니다. 요약의 changed 횟수보다 HTTP 성공과 실제 효과가 중요합니다. 편집 오류로 실행이 중단되면 전체 파일을 고친 뒤 같은 승인 패키지로 다시 실행하세요. 반복 실행은 다시 제외, 검사, 추가를 수행하며, 아무 작업도 하지 않는 배포가 아닙니다.
원래 도구로 상태를 확인하세요.
ansible-playbook inspect.yml
두 노드 모두 courtyard-weekend를 보고하고 풀에서 활성 상태여야 합니다. Website 탭으로 전환하고 새로 고치세요. 제목은 **A little room for a good afternoon.**이어야 합니다. The program을 누르고 세 세션을 확인한 뒤 CSV를 다시 내려받아 새 파일에서 세 행 전체를 확인하세요. 이미 다운로드가 있으면 브라우저가 파일명에 번호를 붙일 수 있습니다. Plan a visit는 계속 정원 입구의 계단 없는 접근 안내로 이동해야 합니다.
업데이트된 프로그램에는 승인된 세 세션이 있으며 목록 옆에서 실제 CSV를 다운로드할 수 있습니다.

이는 준비된 짧은 요청 환경에서 두 노드의 기능적인 업데이트를 보여 줍니다. 모든 트래픽이나 프로덕션 애플리케이션에 대한 무중단 게시 보장은 아닙니다.
요약
상태를 바꾸지 않는 확인 도구를 만들고 Ansible 순차 배포로 실제 사이트와 전체 프로그램 다운로드를 제공했습니다. 위임된 풀 변경과 실제 검사는 각 게시 결정을 해당 관리 대상 서버와 연결합니다.



