{"id":"IL-0009","org":"InfluxData","date":"2022-06-24","docs":["https://argoproj.github.io/cd/"],"title":{"en":"A duplicated generated name made Argo CD replace the InfluxDB Cloud production Application and delete the whole production environment","ko":"ArgoCD Application 이름이 겹쳐 InfluxDB Cloud 운영 환경 하나가 통째로 삭제됐다"},"domain":["kubernetes"],"impact":{"scope":"one production environment, all API traffic","users":"unknown","region":"unknown"},"review":{"written_by":"session","reviewed_at":null,"reviewed_by":null},"lessons":{"en":"The biggest gap was that monitoring did not notice the deletion quickly, which pushed the team toward a reflex rollback; in a stateful system a revert can create a new, empty instance instead of bringing the old one back. Retained volumes and clients that retry writes are what kept data loss at zero. Rendering many objects into one file let an overwrite of an existing object look like an addition, so generated configuration needs one object per file and duplicate detection that compares objects the way Kubernetes does. Stateful resources need protection from pruning, and that protection must cover their Namespace too, or deleting the namespace removes everything inside it.","ko":"무엇보다 모니터링과 경보가 장애를 바로 잡아내지 못했다. 그 탓에 팀은 계획된 복구 대신 롤백부터 시도했다. InfluxDB Cloud 인스턴스 일부나 전체를 잃었을 때 쓸 런북도 없었다. 상태 저장 애플리케이션은 변경을 되돌린다고 원래대로 돌아오지 않고, 데이터 없는 새 인스턴스가 생길 뿐이다. 이런 PR 은 시스템이 머지를 막았어야 했다. 볼륨을 Retain 으로 둔 데다 Telegraf 처럼 쓰기를 재시도하는 클라이언트가 있어서 데이터를 하나도 잃지 않았다."},"related":["IL-0010"],"sources":[{"url":"https://www.influxdata.com/blog/deleting-production-steps-how-fix-it/","kind":"blog","license":"unknown","archived":"http://web.archive.org/web/20260210151159/https://www.influxdata.com/blog/deleting-production-steps-how-fix-it/"}],"summary":{"en":"InfluxDB Cloud runs on Kubernetes and ships 5 to 15 changes to production on a typical workday through Argo CD. Argo CD also manages the definitions of its own Applications and AppProjects across clusters, the \"app of apps\" pattern. Configuration is written in jsonnet and rendered to YAML, and at the time all resources of one application were rendered into a single YAML file. Objects are named (app name)-(cluster name) and live under env/(cluster name)/(app name)/yml. Because of an automation error, the new objects in the pull request were named app01-cluster01 instead of app02-cluster01, so both the existing and the new definition ended up in the same file. Argo CD applied the objects in file order, the last definition won, and the original app01 Application, InfluxDB Cloud's core workload, was replaced and deleted.\n\nRecovery was done by hand in a fixed order. The team restored etcd, which holds metadata on a dedicated cluster separate from the Kubernetes control plane, first. They then recreated the missing Kubernetes objects so the retained PersistentVolumes became available again, and recreated the StatefulSets. Zookeeper volumes were restored from an hourly Velero backup. Kafka's StatefulSet was rebuilt one pod at a time with health and readiness checks disabled, because Kafka does not report ready until its leader is up. Etcd and Kafka could have been restored in parallel, but the team did them one after the other to keep the procedure correct. With Kafka acting as the write-ahead log, writes were accepted again before storage worked, and the write path was given more replicas and higher memory requests and limits to absorb the backlog from Telegraf and other clients. Storage pods were recreated, downloaded their data from object storage, reindexed it and then replayed the unprocessed write-ahead log.\n\nIn the last phase the team reduced replicas of some storage services so they started faster, re-enabled Argo CD to create the objects still missing, turned queries and dashboards back on, restored the normal replica counts, enabled scheduled tasks and ran final QA checks. From the merge to full functionality took just under six hours, and no data was lost: clients that retry, such as Telegraf by default, wrote their data once the service came back.","ko":"InfluxDB Cloud 는 쿠버네티스 위에서 돌고, 팀은 ArgoCD 로 코드와 설정 변경을 운영에 배포한다. 평소 하루에 5건에서 15건의 변경이 나간다. ArgoCD 가 다른 ArgoCD Application 과 AppProject 정의까지 관리하는 app of apps 패턴을 쓴다. 설정 YAML 은 jsonnet 템플릿으로 만들었고, 당시에는 애플리케이션 하나의 리소스를 모두 YAML 파일 하나에 담았다. 객체 이름은 앱 이름과 클러스터 이름을 이어 붙이는 규칙을 따랐다.\n\n자동화 오류로 새 Application 과 AppProject 가 기존 것과 같은 이름으로 만들어졌고, 렌더링된 두 리소스가 같은 YAML 파일에 들어갔다. ArgoCD 는 파일에 적힌 순서대로 객체를 적용하므로 마지막 객체가 앞의 것을 덮어썼다. ArgoCD 가 기존 app01 인스턴스를 새 것으로 바꾸는 과정에서 InfluxDB Cloud 의 핵심 워크로드가 지워졌고, 운영 환경 하나 전체가 즉시 삭제됐다.\n\n데이터 볼륨은 reclaimPolicy: Retain 이라 StatefulSet 과 PVC 가 지워져도 PersistentVolume 과 클라우드 볼륨은 남아 있었다. 팀은 이 점을 바탕으로 복구 계획을 세웠다. 지워진 쿠버네티스 객체를 손으로 다시 만들고, 빠진 데이터는 백업에서 채우고, 상태가 없는 부분은 ArgoCD 가 다시 만들게 했다. etcd 메타데이터, Zookeeper 와 Kafka, 스토리지 엔진 순서로 복구했다. PR 머지부터 전체 기능 복구까지 6시간이 조금 안 걸렸고, 데이터는 하나도 잃지 않았다."},"trigger":{"en":"A merged pull request whose rendered YAML added a new Argo CD Application and AppProject that an automation error had named app01-cluster01, the name of the existing production Application, instead of app02-cluster01; reviewers did not notice the app01 and app02 difference.","ko":"팀원 한 명이 설정 파일에 새 객체 여러 개를 더하는 PR 을 올렸다. 그중 새 ArgoCD Application 과 AppProject 의 이름이 자동화 오류로 app02-cluster01 이 아니라 기존 것과 같은 app01-cluster01 로 정해졌다. 코드 리뷰에서도 app01 과 app02 의 차이를 알아채지 못해 PR 이 그대로 머지됐다."},"severity":"unknown","symptoms":["service-unavailable","resources-deleted","high-error-rate"],"timeline":{"detected_after":null,"resolved_after":"6h"},"date_kind":"published","detection":{"en":"Monitoring and alerting did not detect the issue right away, so the team's first response was to try to roll the change back rather than plan a recovery. The write-up does not say how the team found out.","ko":"모니터링과 경보 시스템은 이 장애를 바로 잡아내지 못했다. 그래서 팀은 계획을 세워 복구하기 전에 먼저 변경을 되돌리려 했다. 팀이 장애를 무엇으로 알게 됐는지는 글에 나오지 않는다."},"components":["argocd","persistent-volume","config-templating"],"root_cause":"config-change","remediation":[{"text":{"en":"stopped the revert of the merged pull request","ko":"머지된 PR 을 되돌리기 시작했다가 곧바로 멈췄다."}},{"text":{"en":"restored the etcd metadata cluster","ko":"etcd 메타데이터 클러스터를 복원했다."}},{"text":{"en":"recreated the missing Kubernetes objects so the retained PersistentVolumes became available, then recreated the StatefulSets","ko":"지워진 쿠버네티스 객체를 다시 만들어 남아 있던 PersistentVolume 을 다시 쓸 수 있게 하고, StatefulSet 을 다시 만들었다."}},{"text":{"en":"restored the Zookeeper volumes from an hourly Velero backup","ko":"Zookeeper 볼륨을 Velero 로 매시간 받아 둔 백업에서 복원했다."}},{"text":{"en":"rebuilt the Kafka StatefulSet one pod at a time with health and readiness checks disabled","ko":"헬스 체크와 readiness 체크를 끈 채 Kafka StatefulSet 을 파드 하나씩 다시 만들었다."}},{"text":{"en":"accepted writes into the Kafka write-ahead log and scaled up the write path with more replicas and higher memory requests and limits","ko":"쓰기 선행 로그 역할을 하는 Kafka 로 다시 쓰기를 받고, 쓰기를 처리하는 컴포넌트의 레플리카 수와 메모리 request 와 limit 을 늘렸다."}},{"text":{"en":"recreated the storage pods, which downloaded data from object storage, reindexed it and replayed the write-ahead log","ko":"스토리지 파드를 다시 만들었다. 파드는 오브젝트 스토리지에서 데이터를 내려받아 인덱싱한 뒤 쓰기 선행 로그에 밀려 있던 데이터를 읽어 들였다."}},{"text":{"en":"re-enabled Argo CD, queries, dashboards and scheduled tasks, restored replica counts and ran final QA checks","ko":"ArgoCD, 쿼리, 대시보드, 예약 작업을 다시 켜고, 레플리카 수를 원래대로 돌린 뒤 최종 QA 점검을 했다."}}],"action_items":[{"text":{"en":"Write runbooks for restoring state when Kubernetes objects such as PersistentVolumeClaims are deleted but the data on the underlying volumes is preserved","ko":"상태를 되살리는 절차를 담은 런북을 만들었다. PVC 같은 쿠버네티스 객체는 지워졌지만 디스크와 볼륨에 데이터는 남아 있을 때 할 일을 자세히 적었다."}},{"text":{"en":"Set every volume in every environment to retain data when its PersistentVolumeClaim is deleted","ko":"모든 환경의 모든 볼륨이 PVC 객체가 지워져도 데이터를 유지하도록 설정했다."}},{"text":{"en":"Improve the process for handling public-facing incidents","ko":"외부에 드러나는 장애를 다루는 절차를 개선했다."}},{"text":{"en":"Store generated YAML as one object per file, so an overwrite of an existing object shows up as such in review","ko":"생성된 YAML 을 객체 하나당 파일 하나로 저장하도록 바꿨다. 예를 들어 etcd Service 는 v1.Service-(namespace).etcd.yaml 파일이 된다. 이제 이번과 같은 PR 은 새 객체를 더하는 변경이 아니라 기존 객체를 덮어쓰는 변경으로 보인다."}},{"text":{"en":"Detect duplicate objects when generating YAML and warn before review, treating objects with the same group, namespace and name as the same even when the apiVersion string differs","ko":"YAML 생성 도구가 중복 객체를 찾아 리뷰를 요청하기 전에 경고하도록 고쳤다. 파일 이름만 보지 않고, apiVersion 이 networking.k8s.io/v1beta1 과 networking.k8s.io/v1 처럼 달라도 네임스페이스와 이름이 같으면 같은 객체로 판단한다."}},{"text":{"en":"Add the Argo CD Prune=false annotation to all stateful resources and to Namespace objects managed by Argo CD","ko":"모든 상태 저장 리소스와 ArgoCD 가 관리하는 Namespace 객체에 Prune=false 어노테이션을 달아, 설정이 잘못돼도 ArgoCD 가 지우지 않게 했다."}},{"text":{"en":"Set FailOnSharedResource=true on Argo CD Applications so Argo CD fails before changing an object another Application manages or managed","ko":"ArgoCD Application 에 FailOnSharedResource=true 옵션을 넣었다. 다른 Application 이 관리하거나 관리했던 객체를 바꾸려 하면 ArgoCD 는 변경을 적용하지 않고 동기화를 실패로 멈춘다."}}],"contributing":["review-missed","no-runbook","no-safeguard-on-destructive-change","monitoring-blind-spot","automation-amplified"],"recurrence_of":null,"symptoms_text":{"en":"Right after a pull request to the infrastructure-as-code repository was merged, every API endpoint of one InfluxDB Cloud production cluster began returning 404 errors. Writes and reads failed alike: no one could collect data, scheduled tasks failed to run and external queries did not work.\n\nBehind the 404s, the production environment itself was gone. Argo CD, the continuous delivery tool that makes each Kubernetes cluster match the YAML in Git, had deleted the Application that held InfluxDB Cloud's core workload, along with the resources it managed: the StatefulSets and PersistentVolumeClaims of the storage engine, Kafka and Zookeeper among them. At the same time it created a workload that was not meant to run on that cluster. The underlying PersistentVolumes and cloud disks of the data volumes survived because they used reclaimPolicy: Retain, but the Zookeeper volumes were lost.\n\nThe cause was hard to see in the merged change. The pull request looked like it only added objects, including a new Argo CD Application and AppProject, but those objects carried the name of the existing production Application, and the problem was hard to spot because it involved an Argo CD collision between a project and an application name.\n\nThe first instinct was to revert the merged change. The team started the revert and stopped almost at once, because Argo CD would then have created a brand-new, empty instance of the application, without the metadata about users, dashboards and tasks and without customer data. Monitoring and alerting had not flagged the problem right away, which is why the reflex was a rollback rather than a planned recovery, and there was no runbook for losing part or all of an InfluxDB Cloud instance.","ko":"PR 하나를 머지한 직후 InfluxDB Cloud 운영 환경 하나가 통째로 사라졌다. 쓰기와 읽기를 비롯한 모든 API 엔드포인트가 404 오류를 냈다.\n\n장애 동안 사용자는 데이터를 수집할 수 없었다. 예약 작업은 실행에 실패했고 외부 쿼리도 동작하지 않았다. ArgoCD 가 기존 app01 인스턴스를 새 객체로 바꾸면서 InfluxDB Cloud 의 핵심 워크로드를 지웠고, 그 클러스터에서 돌면 안 되는 워크로드를 새로 띄웠다. Zookeeper 볼륨도 함께 지워졌다.\n\n모니터링과 경보 시스템은 이 장애를 바로 잡아내지 못했다. 팀은 머지된 PR 의 코드부터 검토했는데, ArgoCD 프로젝트 이름과 애플리케이션 이름이 충돌한 문제라 원인을 찾기 어려웠다.\n\n팀은 먼저 변경을 되돌리기로 했다. 되돌리기를 시작했다가 거의 곧바로 멈췄다. 되돌리면 ArgoCD 가 애플리케이션 인스턴스를 새로 만들고, 새 인스턴스에는 사용자, 대시보드, 작업의 메타데이터가 없고 무엇보다 고객 데이터가 없기 때문이었다."},"updated_at":"2026-11-10T00:00:31.658197Z","url":"https://benchlet.com/incidentlib/app/cases/IL-0009","org_slug":"influxdata"}