{"id":"IL-0113","org":"Webhook Relay","date":"2022-05-10","docs":[],"title":{"en":"The managed GKE control plane of Webhook Relay's main cluster failed because its API server certificates had never been rotated, and a manual credential rotation reported success without taking effect","ko":"한 번도 교체되지 않은 GKE API 서버 인증서 때문에 메인 클러스터의 컨트롤 플레인이 망가져 EU 리전의 백엔드 API, 프런트엔드, 터널이 모두 멈췄다"},"domain":["kubernetes","gcp"],"impact":{"scope":"backend APIs, frontend and tunnels in the EU region; webhook logs in ClickHouse","users":"unknown","region":"EU region"},"review":{"written_by":"session","reviewed_at":null,"reviewed_by":null},"lessons":{"en":"Recovery of existing infrastructure needs a time limit. Throughout the outage the old cluster looked about to recover, and the reluctance to give up on the salvage effort kept the team from starting over with a new cluster much sooner, which would have been the right call.\n\nOn older GKE clusters, check the validity of the cluster CA certificate, for example by decoding masterAuth.clusterCaCertificate from gcloud container clusters describe and reading it with openssl x509. GKE's multiple availability zones made no difference in this failure.","ko":"기존 인프라를 살리는 작업에는 시간 상한을 정해야 한다. 장애 내내 서비스가 곧 살아날 것처럼 보였지만, 기존 클러스터를 훨씬 일찍 포기하고 새 클러스터에서 다시 시작했어야 했다. 이미 들인 노력이 아까워 살리기를 멈추지 못한 것이 판단을 늦췄다.\n\n오래된 GKE 클러스터라면 클러스터 CA 인증서의 유효기간을 `gcloud container clusters describe <cluster name> --zone europe-north1-a --format \"value(masterAuth.clusterCaCertificate)\" | base64 --decode | openssl x509 -text | grep Validity -A 2` 로 확인할 수 있다."},"related":[],"sources":[{"url":"https://webhookrelay.com/blog/2022/05/13/may-10th-outage-gke-controlplane/","kind":"blog","license":"unknown","archived":null}],"summary":{"en":"Webhook Relay has run on GKE since 2017, and the cluster in its main region holds the controllers and backend APIs. On 10 May 2022 that cluster's control plane stopped working properly. About 40 minutes into the outage the team traced it to the Kubernetes API server certificates: older GKE clusters were issued five-year certificates (newer ones get 30 years), and this cluster's certificates had never been rotated.\n\nThe team started a manual credential rotation with gcloud container clusters update --start-credential-rotation. The command succeeded but changed nothing: the nodes that the rotation should have recreated stayed as they were, so the node pools were recreated by hand. Upgrading the control plane did not help either. Workloads came back, but in-cluster service account authentication failed, so the pods could not reach Cloud SQL, Pub/Sub or Cloud Storage. GKE's multiple availability zones made no difference to this failure.\n\nBy 14:30 the team was already copying the main cluster's services into a new cluster. Load balancers and persistent volume claims still belonged to the old cluster, so the Kubernetes Service records were deleted there to free the load balancers for the new cluster, and the StatefulSet was detached from the old cluster as well. Webhook routes briefly wrote to a fresh disk; at 17:30 all writes were switched to the main disk, and at 18:30 the data from the temporary ClickHouse instance had been exported and inserted into the main one.\n\nThe team says the outage would have been much shorter had it known the managed cluster was beyond repair, because the cluster always seemed about to recover. Moving to a new cluster has its own work (moving persistent disks, detaching load balancers) but would still have been quicker.","ko":"Webhook Relay 는 2017년부터 GKE 를 써 왔고, 컨트롤러와 백엔드 API 가 있는 메인 리전을 GKE 클러스터에서 운영했다. 2022년 5월 10일 이 클러스터의 관리형 컨트롤 플레인이 망가져 큰 장애가 났다. 팀은 12시 40분에 원인이 쿠버네티스 API 서버 인증서임을 알아냈다. 예전에 만든 GKE 클러스터는 5년짜리 인증서를 받았고 지금 만드는 클러스터는 30년짜리를 받는데, 이 클러스터에서는 인증서 교체가 한 번도 일어나지 않았다.\n\n팀은 수동 인증서 교체를 시작했다. 교체 과정에서 노드가 다시 만들어져야 했지만 아무 일도 일어나지 않아 노드 풀을 손으로 다시 만들었다. 교체 작업은 성공했다고 나왔지만 실제로는 효과가 없었고, 컨트롤 플레인 업그레이드도 도움이 되지 않았다. 파드는 돌았지만 서비스 어카운트 인증이 실패해 뒤에 있는 서비스에 닿지 못했다.\n\n인증 오류를 확인한 뒤 팀은 서비스를 새 클러스터로 옮기기 시작했다. 로드 밸런서와 PVC 가 기존 클러스터에 묶여 있어서 먼저 떼어 내야 했다. 16시 50분 기존 클러스터에서 쿠버네티스 Service 레코드를 지운 뒤에야 새 클러스터에 붙일 수 있었다. 17시 10분 새 백엔드가 떴지만 웹훅 라우트가 새 디스크에 쓰고 있었다. 17시 30분 StatefulSet 도 떼어 내 모든 쓰기를 메인 디스크로 돌렸고, 18시 30분 임시 ClickHouse 인스턴스의 데이터를 내보내 메인 인스턴스에 다시 넣었다.\n\n장애 내내 기존 클러스터가 곧 살아날 것처럼 보였기 때문에 복구가 길어졌다. 팀은 관리형 클러스터가 완전히 망가졌다는 것을 알았다면 처음부터 새 클러스터로 옮겼을 것이고, 디스크를 옮기고 로드 밸런서를 떼어 내는 수고를 감안해도 훨씬 빨랐을 것이라고 적었다. 여러 가용 영역에 걸친 GKE 구성은 이번 장애에서 도움이 되지 않았다."},"trigger":null,"severity":"unknown","symptoms":["service-unavailable","control-plane-slow","control-plane-unresponsive","stale-api-reads","credentials-unavailable"],"timeline":{"detected_after":null,"resolved_after":"6h 30m"},"date_kind":"incident","detection":{"en":"The health check system sent a notification that the service was down; the team then found the main dashboard offline.","ko":"헬스체크 시스템이 서비스가 내려갔다는 첫 알림을 보냈다. 팀은 처음에 일시적 오류로 보고 자동 복구를 기대했지만, 메인 대시보드가 내려간 것을 확인하고 바로 대응을 시작했다."},"components":["gke-control-plane","kube-apiserver","node-pool","rbac-service-account"],"root_cause":"certificate-expiry","remediation":[{"text":{"en":"recreated the node pools by hand after the credential rotation failed to recreate them","ko":"인증서 교체가 노드를 다시 만들지 않아 13시 20분 노드 풀을 손으로 다시 만들었다."}},{"text":{"en":"duplicated the main cluster's services into a newly created GKE cluster","ko":"14시 30분에는 이미 메인 클러스터의 서비스를 새로 만든 GKE 클러스터에 복제하고 있었다."}},{"text":{"en":"deleted the Kubernetes Service records in the old cluster so its load balancers could be attached to the new cluster","ko":"16시 50분 기존 클러스터에서 쿠버네티스 Service 레코드를 지워 로드 밸런서를 새 클러스터에 붙일 수 있게 했다."}},{"text":{"en":"detached the StatefulSet from the old cluster and switched all writes to the main disk","ko":"17시 30분 StatefulSet 을 기존 클러스터에서 떼어 내고 모든 쓰기를 메인 디스크로 돌렸다."}},{"text":{"en":"exported the data from the temporary ClickHouse instance and inserted it into the main instance","ko":"18시 30분 임시 ClickHouse 인스턴스의 데이터를 내보내 메인 인스턴스에 다시 넣었다."}}],"action_items":[{"text":{"en":"Change the deployment manifests so services can be switched between clusters quickly without a complicated persistent disk migration","ko":"디스크 데이터를 복잡하게 옮기지 않고도 클러스터 사이를 빠르게 바꿀 수 있도록 배포 매니페스트를 고쳤다."}}],"contributing":["limited-debug-access","single-point-of-failure"],"recurrence_of":null,"symptoms_text":{"en":"At 12:00 EST the health check system reported that the backend APIs, the frontend and the tunnels in the EU region were down. The team first took it for a transient error, such as an interrupted client request or a server evicted for memory or CPU pressure, but the main dashboard was offline as well.\n\nTen minutes in, everything the cluster reported looked inconsistent, as if the nodes were down or in the middle of an upgrade, although no maintenance window was scheduled. Recycling the node pools took a long time, the cluster timed out, and operations failed several times before they succeeded.\n\nThe GKE console neither looked clearly broken nor clearly healthy. Node pools could be listed, created and managed, deployments could be edited, logs could be viewed and the control plane version could be upgraded, but every operation was very slow. kubectl could list and fetch objects but could not update them, show logs or port-forward, and some of the workload information it returned was clearly stale.\n\nAfter the nodes were recreated, workloads ran but service-account-based authentication inside the cluster failed. Backend services could not connect to Cloud SQL, Pub/Sub or Cloud Storage. A second cluster created during recovery also misbehaved: its control plane became unresponsive every few minutes. Port forwarding to the ClickHouse database that stores the webhook logs failed, and logs could not be read through the Kubernetes API either.","ko":"2022년 5월 10일 낮 12시 EST 에 헬스체크 시스템이 경보를 울렸다. EU 리전의 백엔드 API, 프런트엔드, 터널이 모두 내려가 있었다. 처음에는 클라이언트 쪽에서 요청이 끊겼거나, 메모리나 CPU 사용량 때문에 축출된 서버에 요청이 닿은 일시적 오류라고 생각했다. 메인 대시보드가 내려간 것을 보고 바로 대응을 시작했다.\n\n12시 10분에는 쿠버네티스 클러스터의 정보가 서로 맞지 않았다. 노드가 죽었거나 업그레이드 중인 것처럼 보였다. 몇 년 전 GKE 자동 유지보수 업그레이드가 잘못됐을 때와 비슷했지만 유지보수 시간대가 아니었다. 12시 20분에는 노드 풀 재생성이 오래 걸렸고, 클러스터 요청이 타임아웃 났고, 작업이 여러 번 실패한 뒤에야 성공했다.\n\nGKE 콘솔은 완전히 정상도 아니고 확실히 망가진 것도 아닌 상태로 보였다. 콘솔에서 노드 풀을 보고 만들고 관리할 수 있었고, 노드는 정상 상태가 되어 워크로드를 띄웠지만 모든 작업이 매우 느렸다. 디플로이먼트를 고치고 로그를 보는 것도, 컨트롤 플레인 버전을 올리는 것도 됐다. 반면 kubectl 은 오브젝트를 조회할 수는 있었지만 로그를 보거나 포트 포워딩을 할 수 없었고 오브젝트를 수정할 수 없었다. 일부 워크로드 정보는 분명히 오래된 값으로 나왔다.\n\n13시 50분 워크로드가 다시 떴지만 클러스터 안의 서비스 어카운트 기반 인증이 실패했다. 백엔드 서비스가 Cloud SQL, PubSub, GCS 에 연결하지 못했다. 14시 30분 무렵 서비스를 복제하고 있던 새 클러스터도 몇 분마다 컨트롤 플레인이 응답하지 않았다. 15시에는 포트 포워딩 명령이 실패해 웹훅 로그를 저장하는 ClickHouse 데이터베이스에 접속할 수 없었고, 쿠버네티스 API 로도 로그를 볼 수 없었다."},"updated_at":"2026-11-10T00:00:33.996389Z","url":"https://benchlet.com/incidentlib/app/cases/IL-0113","org_slug":"webhook-relay"}