FDE Capstone: The Warehouse Got the Same Order Three Times
Install reported done, but the agent died on startup
한국어 원문으로 표시합니다.
목표
고객 호스트의 설치 요구사항을 점검해 pass·warn·fail 을 JSON 보고서와 종료 코드로 내는 사전 점검 스크립트를 만든다. 채점기가 차린 여러 환경에서 판정이 맞아야 하고, 점검은 아무것도 고치지 않아야 한다.
왜 중요한가
고객 현장의 설치 창은 한 번뿐인 경우가 많다. 지난번 설치는 "완료" 로 끝났지만 포트는 사내 프록시가 쓰고 있었고 인증서는 이미 만료돼 있었다. 사전 점검은 이런 사실을 설치 전에 근거와 함께 드러내는 도구다. 판정만이 아니라 관측값을 남겨야 고객 담당자가 다시 확인할 수 있고, 종료 코드가 일정해야 설치 자동화가 진행과 중단을 가를 수 있다. 채점기는 여러분의 문구를 믿지 않는다. 임시 디렉터리에 점유 포트·TIME_WAIT 포트·임박/만료 인증서·없는 디렉터리를 직접 만들고, 포트 번호·기준값·경로를 실행마다 바꿔 스크립트를 실행한다.
단계
- 고객 메모를 읽고 요구사항 7종을 /root/preflight/spec.json 에 계약의 키 이름으로 적는다. 옛 문서의 값과 지원 정책을 구분한다.
- /root/preflight/preflight.py 에 python_min 과 ports 점검, 보고서(summary·exit_code·checks), 종료 코드 0·1·2 를 구현한다. 명세에 없는 키의 점검은 하지 않는다.
- preflight.py 의 포트 점검이 TIME_WAIT 만 남은 포트를 사용 중으로 오탐하지 않게 고친다. 듣는 포트는 여전히 fail 이어야 한다.
- preflight.py 에 disk 점검을 넣는다. 여유 MiB 를 observed 에, 잰 경로를 measured_path 에 적고, 아직 없는 경로는 만들지 말고 가장 가까운 부모를 잰다.
- preflight.py 에 certs 점검을 넣는다. notAfter 를 epoch 초로 observed 에, 남은 일수를 days_left 에 적고, 만료·못 읽음은 fail, warn_days 미만은 warn 이다.
- preflight.py 에 files·hosts·writable_dirs 점검을 넣는다. observed 는 계약의 상태 이름을 쓰고, 쓰기 시험 파일은 지우며 없는 디렉터리는 만들지 않는다.
- 정상·경고·실패 호스트 전체 명세와 깨진 명세를 구분하는지 확인한다. 종료 코드는 0·1·2, 명세를 못 읽으면 보고서 없이 3 이다.
- spec.json 으로 이 호스트를 점검해 /root/preflight/report.json 을 남기고, /root/preflight/decision.json 에 보고서 해시·판정(go·go-with-warnings·no-go)·blockers·warnings 를 적는다.
참고
- 재료: 고객 메모 /opt/lab/p1a-preflight/customer-host.md, 실행 계약 /opt/lab/p1a-preflight/CONTRACT.md (명세 키·점검 id·observed·종료 코드), 고객이 준 설정·인증서 /opt/lab/p1a-preflight/host/etc/stocksync/
- 직접 시험: python3 /root/preflight/preflight.py --spec /root/preflight/spec.json --report /tmp/r.json; echo $?
- 점유 포트 흉내: python3 -m http.server 18080 & 로 띄운 뒤 그 포트를 명세에 넣어 본다. 끝나면 그 작업의 PID 만 골라 끈다.
- 인증서 만들기: openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -nodes -keyout /tmp/k.pem -out /tmp/c.crt -days 5 -subj /CN=t
- 흔한 실수: 버전을 문자열로 비교하기, SO_REUSEADDR 없이 bind 하기, 없는 디렉터리를 만들어 주기, 쓰기 시험 파일을 남기기.
- 사전 점검 통과는 설치 성공의 보장이 아니다. 명세에 적은 조건만 확인한다.
고객 메모를 명세로 옮기기
고객 메모의 요구사항 7종을 계약의 키 이름으로 /root/preflight/spec.json 에 적으세요.
메모에는 옛 문서의 값과 '돌아가긴 했다' 는 경험담이 섞여 있습니다. 점검 기준은 이번 판과 공급사 지원 정책입니다. 포트는 정수 배열, disk·certs 는 객체 배열입니다.
파이썬 판과 포트부터 판정하기
/root/preflight/preflight.py 가 python_min·ports 를 점검하고 보고서와 종료 코드 0·1·2 를 내게 하세요.
포트가 비었는지는 0.0.0.0 에 실제로 bind 해 보면 압니다. 판 비교는 sys.version_info 와 정수 튜플로 합니다. summary 는 fail 이 하나라도 있으면 fail 입니다. 채점기는 매번 다른 포트와 판 기준으로 실행합니다.
끝난 연결을 점유로 오탐하지 않기
/root/preflight/preflight.py 의 포트 점검이 TIME_WAIT 만 남은 포트는 free, 듣는 포트는 in_use 로 구분하게 하세요.
서버 쪽이 먼저 닫은 연결은 그 포트에 TIME_WAIT 를 남깁니다. 옵션 없는 bind 는 이것도 막힙니다. 소켓 옵션 하나로 TIME_WAIT 는 통과하고 진짜 리스너는 여전히 막히게 할 수 있습니다.
여유 공간을 경고와 실패로 나누기
/root/preflight/preflight.py 에 disk 점검을 넣어 observed(여유 MiB)·measured_path·pass/warn/fail 을 내게 하세요.
shutil.disk_usage 의 free 를 1024*1024 로 나눈 몫입니다. 경로가 없으면 os.path.dirname 으로 올라가며 존재하는 곳을 찾되 만들지는 않습니다. 채점기는 지금 여유 공간을 기준으로 기준값을 바꿔 가며 부족한 디스크를 흉내 냅니다.
인증서 남은 날짜를 notAfter 로 재기
/root/preflight/preflight.py 에 certs 점검을 넣어 observed(notAfter epoch 초)·days_left·판정을 내게 하세요.
openssl x509 -enddate -noout -in 파일 의 출력에서 notAfter= 뒤를 ssl.cert_time_to_seconds 로 바꿉니다. 읽지 못한 파일은 observed null 로 fail 입니다. 채점기는 여유·임박·만료·깨진 인증서를 만들어 섞습니다.
설정·호스트 이름·쓰기 권한 확인하기
/root/preflight/preflight.py 에 files·hosts·writable_dirs 점검을 넣으세요. 없는 디렉터리를 만들지 말고 쓰기 시험 파일은 지웁니다.
파일은 없음·파일 아님·빈 파일을 구분합니다. 이름 해석은 socket.getaddrinfo 가 실패하면 socket.gaierror 를 냅니다. 쓰기는 os.access 대신 tempfile.mkstemp 로 실제로 만들어 보고 지웁니다.
호스트 세 대와 깨진 명세 구분하기
/root/preflight/preflight.py 가 전체 명세에서 정상·경고·실패를 종료 코드 0·1·2 로, 읽을 수 없는 명세를 보고서 없이 3 으로 끝내는지 확인하세요.
명세 키의 순서가 바뀌어도 점검 id 는 명세에 적힌 것과 정확히 같아야 합니다. 명세 JSON 이 깨졌다면 판정할 근거가 없으니 보고서를 쓰지 않습니다.
고객 호스트에 go / no-go 내리기
spec.json 으로 점검해 /root/preflight/report.json 을 남기고, /root/preflight/decision.json 에 report_sha256·decision·blockers·warnings 를 적으세요.
decision 은 summary 에서 나옵니다(pass→go, warn→go-with-warnings, fail→no-go). blockers 는 fail 점검 id, warnings 는 warn 점검 id 입니다. 채점기는 지금 다시 점검해 보고서가 낡지 않았는지와 해시를 확인합니다. 막는 항목을 고쳐서 통과시키는 단계가 아닙니다.