Incident Library
  • 원인
  • 구성 요소
  • 증상
  • 기여 요인
  • 사례

사례3건

3건 중 1~3번째

  1. 증상

    Aqua 의 쿠버네티스 허니팟 클러스터에 공격이 들어왔다. 공격자는 Secret 목록을 조회하는 HTTP 요청 몇 개를 보내고, kube-system 네임스페이스의 리소스 목록을 조회하는 API 요청 두 개로 클러스터 정보를 모았다.

    촉발

    API 서버가 잘못 설정되어, 권한을 가진 익명 사용자의 인증 없는 요청을 받아들였다.

    원인
    구성 요소
    기여 요인
    감지

    Aqua 가 운영하는 쿠버네티스 허니팟에 들어온 공격을 기록하고 분석해 찾았다. 허니팟에 일부러 노출해 둔 AWS 액세스 키가 쓰이자 비컨 신호가 왔다.

    교훈

    API 서버에 익명 접근을 허용하는 설정 실수 하나로 클러스터 전체를 내줄 수 있다. 공격자가 ClusterRoleBinding 으로 지속적인 접근 경로를 만들어 두면 잘못된 설정을 고친 뒤에도 계속 들어올 수 있다. 공격자는 system:controller: 로 시작하는 정상 구성 요소 같은 이름과 kube-controller-manager 를 닮은 이미지 이름으로 감사 로그와 사람의 눈을 피한다. Aqua 는 Trivy 같은 도구로 취약점, 노출된 Secret, 잘못된 설정을 찾으라고 권한다.

  2. 증상

    PR 하나를 머지한 직후 InfluxDB Cloud 운영 환경 하나가 통째로 사라졌다. 쓰기와 읽기를 비롯한 모든 API 엔드포인트가 404 오류를 냈다.

    촉발

    팀원 한 명이 설정 파일에 새 객체 여러 개를 더하는 PR 을 올렸다. 그중 새 ArgoCD Application 과 AppProject 의 이름이 자동화 오류로 app02-cluster01 이 아니라 기존 것과 같은 app01-cluster01 로 정해졌다. 코드 리뷰에서도 app01 과 app02 의 차이를 알아채지 못해 PR 이 그대로 머지됐다.

    원인
    구성 요소
    기여 요인
    감지

    모니터링과 경보 시스템은 이 장애를 바로 잡아내지 못했다. 그래서 팀은 계획을 세워 복구하기 전에 먼저 변경을 되돌리려 했다. 팀이 장애를 무엇으로 알게 됐는지는 글에 나오지 않는다.

    조치
    • 머지된 PR 을 되돌리기 시작했다가 곧바로 멈췄다.
    • etcd 메타데이터 클러스터를 복원했다.
    • 지워진 쿠버네티스 객체를 다시 만들어 남아 있던 PersistentVolume 을 다시 쓸 수 있게 하고, StatefulSet 을 다시 만들었다.
    • Zookeeper 볼륨을 Velero 로 매시간 받아 둔 백업에서 복원했다.
    • 헬스 체크와 readiness 체크를 끈 채 Kafka StatefulSet 을 파드 하나씩 다시 만들었다.
    • 쓰기 선행 로그 역할을 하는 Kafka 로 다시 쓰기를 받고, 쓰기를 처리하는 컴포넌트의 레플리카 수와 메모리 request 와 limit 을 늘렸다.
    • 스토리지 파드를 다시 만들었다. 파드는 오브젝트 스토리지에서 데이터를 내려받아 인덱싱한 뒤 쓰기 선행 로그에 밀려 있던 데이터를 읽어 들였다.
    • ArgoCD, 쿼리, 대시보드, 예약 작업을 다시 켜고, 레플리카 수를 원래대로 돌린 뒤 최종 QA 점검을 했다.
    후속 조치
    • 상태를 되살리는 절차를 담은 런북을 만들었다. PVC 같은 쿠버네티스 객체는 지워졌지만 디스크와 볼륨에 데이터는 남아 있을 때 할 일을 자세히 적었다.
    • 모든 환경의 모든 볼륨이 PVC 객체가 지워져도 데이터를 유지하도록 설정했다.
    • 외부에 드러나는 장애를 다루는 절차를 개선했다.
    • 생성된 YAML 을 객체 하나당 파일 하나로 저장하도록 바꿨다. 예를 들어 etcd Service 는 v1.Service-(namespace).etcd.yaml 파일이 된다. 이제 이번과 같은 PR 은 새 객체를 더하는 변경이 아니라 기존 객체를 덮어쓰는 변경으로 보인다.
    • YAML 생성 도구가 중복 객체를 찾아 리뷰를 요청하기 전에 경고하도록 고쳤다. 파일 이름만 보지 않고, apiVersion 이 networking.k8s.io/v1beta1 과 networking.k8s.io/v1 처럼 달라도 네임스페이스와 이름이 같으면 같은 객체로 판단한다.
    • 모든 상태 저장 리소스와 ArgoCD 가 관리하는 Namespace 객체에 Prune=false 어노테이션을 달아, 설정이 잘못돼도 ArgoCD 가 지우지 않게 했다.
    • ArgoCD Application 에 FailOnSharedResource=true 옵션을 넣었다. 다른 Application 이 관리하거나 관리했던 객체를 바꾸려 하면 ArgoCD 는 변경을 적용하지 않고 동기화를 실패로 멈춘다.
    교훈

    무엇보다 모니터링과 경보가 장애를 바로 잡아내지 못했다. 그 탓에 팀은 계획된 복구 대신 롤백부터 시도했다. InfluxDB Cloud 인스턴스 일부나 전체를 잃었을 때 쓸 런북도 없었다. 상태 저장 애플리케이션은 변경을 되돌린다고 원래대로 돌아오지 않고, 데이터 없는 새 인스턴스가 생길 뿐이다. 이런 PR 은 시스템이 머지를 막았어야 했다. 볼륨을 Retain 으로 둔 데다 Telegraf 처럼 쓰기를 재시도하는 클라이언트가 있어서 데이터를 하나도 잃지 않았다.

  3. 증상

    2021년 3월 11일 15:35 EST 에 CircleCI UI 에 접속할 수 없게 됐다. API 는 별도 인그레스로 들어오기 때문에 계속 응답했고 빌드도 계속 돌았다.

    촉발

    테스트 Kubernetes 클러스터를 대상으로 한 배포가 테스트 클러스터와 운영 클러스터 양쪽에서 Traefik 과 Calico 리소스를 지웠다.

    원인
    구성 요소
    기여 요인
    감지

    알림을 받은 엔지니어들이 조사를 시작했다. 상태 페이지에는 20:45 UTC 에 첫 공지가 올라왔다.

    조치
    • 지워진 Traefik 을 복구했다.
    • Calico 를 복구해 운영 클러스터의 파드가 다시 고객 요청을 처리하게 했다.
    후속 조치
    • Kubernetes 네트워크의 여러 계층을 모두 다루도록 네트워크 수준 문서와 런북을 보강한다.
    • 네트워크 인프라 쪽 모니터링과 알림을 개선했다.
    • 운영 클러스터와 스테이징 클러스터가 서로 간섭하지 않도록 둘을 더 확실히 떼어 놓는 안전장치를 추가한다.
    교훈

    테스트 클러스터용 배포가 운영 클러스터의 리소스를 건드릴 수 있으면 스테이징 작업이 곧 운영 장애가 된다. 스테이징 배포가 운영에 영향을 줬다는 사실이 바로 보이지 않아 조사가 DNS 와 AWS 에서 시작됐다. Traefik 만 되살려서는 복구되지 않았고 Calico 와 Traefik 의 관계까지 알아야 했다. CircleCI 는 Kubernetes 네트워크의 모든 계층을 담은 문서와 런북이 필요하다고 정리했다.

내 클러스터에도 있는지 확인하기

이 3건에서 가장 많이 나오는 구성 요소: Argo CD, Calico. kubegap 은 Claude Code 나 Codex 에서 쿠버네티스 클러스터를 읽기만 하며 이런 틈을 점검합니다. 점검 항목의 근거는 이 라이브러리와 같은 공개 보고서입니다.

원인별2