AWS 2017년 스토리지 장애, 명령 하나가 4시간이 됐다

2017년 AWS 스토리지 서비스가 약 4시간 멈추자 인터넷의 상당 부분이 함께 멈췄다. 시작은 잘못 입력한 명령 하나였다. 그런데 진짜 문제는 사람보다 구조 쪽에 있었다.

2017년 2월, AWS 의 스토리지 서비스가 약 4시간 동안 제대로 돌지 않았다. 인터넷의 상당 부분이 같이 멈췄다.

시작은 명령어 하나였다. 정해진 절차대로 서버 몇 대를 점검 대상에서 빼는 작업이었는데, 입력이 잘못 들어가서 의도보다 훨씬 많은 서버가 빠져 버렸다.

이 이야기를 "누가 실수했다"로 읽으면 놓치는 게 많다. 사람은 실수한다. 그건 상수다.

문제는 그 실수가 그렇게 큰 결과로 이어질 수 있는 구조였다. 명령 하나가 아무 검증 없이 대규모 변경을 일으킬 수 있었고, 되돌리는 데는 오래 걸렸다.

실수보다 실수가 닿는 범위

이 사고를 뜯어 보면 구조적인 문제가 세 군데 보인다.

먼저 영향 범위에 제한이 없었다. 명령 한 번으로 시스템의 상당 부분이 바뀔 수 있었다. 사람이 실수하는 건 못 막아도, 실수 한 번이 닿는 범위는 설계로 줄일 수 있는데 말이다.

재시작도 예상보다 한참 오래 걸렸다. 시스템이 오래 켜져 있는 사이에 규모는 커졌는데, 전체를 다시 켜 본 적은 없었다. 실제 재시작 시간은 아무도 모르는 값이었고, 막상 해 보니 훨씬 길었다.

마지막은 의존성이 빙 돌아 제자리로 오는 구조였다. 상태를 알려 주는 페이지가 바로 그 서비스에 기대고 있어서, 장애가 났는데 장애가 났다고 알릴 수가 없었다.

이 대목은 지금 봐도 아찔하다.

이 셋이 한목소리로 말하는 게 있다. 정상 동작만 검증하고 회복은 검증하지 않았다.

시스템은 매일 정상 동작을 검증받는다. 트래픽이 흐르고, 테스트가 돌고, 지표가 초록색이니까. 그런데 "전부 껐다가 다시 켜면 얼마나 걸리나"는 아무도 시험하지 않는다. 그게 필요한 순간이 오기 전까지는.

그리고 그 순간은 꼭 제일 나쁠 때 온다. 거의 법칙이다.

한 번의 실수가 닿는 곳에 울타리 치기

한 번의 실수가 닿는 범위를 제한한다:

코드
· 1) 파괴적 명령에 영향 범위 상한을 둔다
 remove-servers --count 200
 한 번에 최대 N% 이상은 아예 거부한다
 넘으면 추가 승인을 요구한다

· 2) 실행 전에 무엇이 바뀌는지 보여 준다
 dry-run 을 기본값으로, 실행은 명시적 플래그로
 '이 명령은 서버 200대를 제거합니다. 계속?'

· 3) 되돌리는 경로를 먼저 확인한다
 이 작업의 되돌리기는 무엇이고 얼마나 걸리는가
 모르면 실행하지 않는다

· 4) 회복 시간을 실제로 재 둔다
 정기적으로 전체 재시작을 연습한다
 해 본 적 없는 절차의 소요 시간은 아무도 모른다

✕ 문제 지점 · ✓ 해법 · 그대로 돌리기 전에 자기 환경에 맞게 고친다.

"더 조심하자"는 왜 안 통할까

사람의 실수를 없애려는 시도는 대개 실패한다. 더 조심하라고 하거나, 절차서를 더 자세히 쓰거나, 교육을 늘리는 식으로는. 사람은 계속 실수하고, 새벽이나 급할 때는 더 그렇다.

그래서 방향을 틀어야 한다. 실수가 일어난다는 전제 위에서 결과를 작게 만드는 것.

방법은 몇 가지가 있다.

영향 범위에 상한을 두는 것. 명령 하나가 전체의 몇 퍼센트 이상을 건드리려 하면 거부하거나 추가 승인을 받게 한다. 제일 직접적인 방어다.

무엇이 바뀌는지 먼저 보여 주는 것. 실행 전에 대상 목록과 개수를 눈앞에 띄우면 오타가 그 자리에서 걸린다. 확인 없이 바로 실행되는 게 기본값이어선 안 된다.

되돌리는 길을 미리 아는 것. "이걸 되돌리려면 뭘 해야 하고 얼마나 걸리나"에 답을 못 한다면 그 작업은 아직 준비가 덜 됐다.

그리고 회복 시간을 실제로 재 두는 것. 이 사고가 남긴 가장 큰 교훈이 이거라고 본다. 한 번도 해 본 적 없는 절차의 소요 시간은 추정치가 아니라 미지수다. 정기적으로 연습하면 그 값을 알게 되고, 알면 계획을 세울 수 있다.

의존성 순환도 점검해야 한다. 장애를 알리는 수단이 장애 대상에 기대고 있으면 안 되니까. 상태 페이지, 알림, 배포 도구가 각각 무엇에 의존하는지 그려 보면 깜짝 놀랄 순환이 나오는 일이 많다.

다음 새벽이 오기 전에

  1. 파괴적 작업엔 영향 범위 상한을 둔다. 한 번에 전체의 일정 비율 이상을 건드리려 하면 거부하게.
  2. 실행 전 확인을 기본값으로 만든다. 무엇이 몇 개 바뀌는지 보여 주고 명시적인 동의를 받는다.
  3. 되돌리는 길과 거기 걸리는 시간을 모르면 아직 실행할 때가 아니다. 이 질문에 답할 수 있어야 준비가 된 거다.
  4. 해 본 적 없는 절차의 소요 시간은 미지수다. 회복 절차는 정기적으로 연습해 두자.
  5. 장애를 알리는 수단이 장애 대상에 기대면 안 된다. 상태 페이지·알림·배포 도구의 의존성을 그려 본다.
  6. "다음부터 조심하자"는 대책이 아니다. 개인의 주의력에 기대는 대책은 다음 새벽에 똑같이 무너진다.
  7. 위험한 명령은 도구로 감싼다. 사람이 원시 명령을 직접 치는 순간을 줄이는 거다.
  8. 사고 회고에서 물을 건 "누가 했나"가 아니라 "왜 그게 가능했나"다.
  9. 긴급 상황용 우회 경로는 남겨 두되 기록이 남게 한다. 안전장치 때문에 진짜 장애 대응을 못 하면 안 되니까.

우리 도구에서 실수 한 번은 어디까지 가나

한 번의 실수가 어디까지 닿는가:

코드
· 우리 도구 중 한 번에 큰 변경이 가능한 것
 서버 제거 · 트래픽 전환 · 설정 배포 · 데이터 삭제
 각각에 영향 범위 상한이 있는가

· 실행 전 확인이 기본값인가
 확인 없이 실행되는 게 기본이면 그게 위험 지점이다

· 되돌리기 경로와 소요 시간을 아는가
 각 위험 작업마다 답할 수 있어야 한다

· 회복 절차를 마지막으로 연습한 게 언제인가
 1년이 넘었다면 그 절차는 사실상 검증되지 않았다

· 장애를 알리는 수단의 의존성을 그려 본다
 상태 페이지가 장애 대상에 의존하면 안 된다

핵심 한 줄사람은 실수한다. 설계할 것은 실수의 여부가 아니라 그 결과의 크기다.

브루니가 적는다

색을 바꾸며 숨는 카멜레온이다. 운영에서 실제로 터지는 문제를 한 편씩 적는다. 같은 실수를 두 번 하지 않으려고 쓰는 기록이다.

장애·확장 글 22편 모두 보기 →