ME 자동화 조치, 꼭 필요한가?
AWS에서는 고객들이 사용 중인 EC2 인스턴스를 재부팅하는 등 자체적으로 유지관리 조치를 수행합니다. AWS에서 수행하는 유지관리의 종류는 아래와 같습니다.
- Instance stop(인스턴스 중지): 예약된 시간에 인스턴스가 중지됩니다. 인스턴스를 다시 시작하면 새 호스트로 마이그레이션됩니다. 이러한 유형은 Amazon EBS가 지원하는 인스턴스에만 적용됩니다.
- Instance retirement(인스턴스 만료): 예약된 시간에 인스턴스가 Amazon EBS에서 지원되는 경우 중지되거나 인스턴스 스토어에서 지원되는 경우 종료됩니다.
- Instance reboot(인스턴스 재부팅): 예약된 시간에 인스턴스가 재부팅됩니다.
- System reboot(시스템 재부팅): 예약된 시간에 인스턴스의 호스트가 재부팅됩니다.
- System maintenance(시스템 유지 관리): 예약된 시간에 네트워크 또는 전력 유지 관리로 인스턴스가 일시적인 영향을 받을 수 있습니다.
만약 고객이 사용 중인 인스턴스가 임의로 재부팅될 경우 서비스에 심각한 문제가 생길 수 있다면, 이러한 유지관리 이벤트(Maintenance Events, ME)를 미리 파악하여 적절한 조치를 취해두어야 합니다.
보통은 이러한 조치를 수동으로 수행합니다. AWS 콘솔에 들어가서 직접 확인하거나 모니터링 툴을 통해 ME 정보를 파악하여 수동으로 인스턴스를 재부팅해준 후 프로세스를 실행하는 것입니다. 하지만 동일한 작업을 매번 반복적으로 수행하다 보면 어느 순간 깨달음이 생기기 마련이지요. 진정한 DevOps라면 이 상황을 그냥 내버려둘 수 없습니다.
이번 글에서는 EC2 인스턴스를 이용 중인 사용자 입장에서 AWS의 ME에 대한 대응을 어떻게 자동화할 수 있는지 구체적인 시나리오를 통해 다뤄보려고 합니다.
ME 자동화 프로세스
본 예제에 사용된 기술은 아래와 같습니다. 아래 기술에 대해 어느 정도 배경지식을 가지고 이번 글을 읽어보시면 더 많은 도움이 될 수 있을 것입니다.
- python – 운영 환경에서 많이 쓰이는 언어 중 하나
- boto3 – AWS에서 제공하는 python용 sdk
- ansible – 인프라 프로비저닝 자동화 도구
1. boto3로 ME 정보 가져오기
ME 자동화 조치를 하기 위해서는 우선 어떤 인스턴스에 ME가 예정되어 있는지 파악해야 합니다. 이를 위해 AWS sdk 파이썬 라이브러리인 boto3를 사용해보겠습니다.
boto3에서 ME 예정인 인스턴스를 가져오는 api는 health 등 여러 방법이 있지만 여기에서는 ec2 클라이언트를 이용해서 가져와 보겠습니다.
import boto3
session = boto3.Session()
ec2 = session.client(‘ec2’, region_name=’ap-northeast-2’)
filter_code = {
'Name': 'event.code',
'Values': [
'instance-reboot',
'system-reboot',
'system-maintenance',
'instance-retirement',
'instance-stop'
]
}
instance_statuses = ec2.describe_instance_status(Filters=[filter_code])
instance_list_for_me = []
for i in instance_statuses['InstanceStatuses']:
instance_list_for_me.append(i['InstanceId'])
위 코드는 ‘instance-reboot’을 포함한 모든 이벤트로 필터링하여 가져온 인스턴스 상태 정보(‘InstanceStatuses’)에서 인스턴스 id를 리스트에 추가하는 코드입니다.
예제에서는 region_name을 ‘ap-northeast-2’로 지정하고 있습니다. 만약 이용자가 여러 리전에서 인스턴스를 사용하고 있다면 해당 지역 코드를 리스트로 추가하여 지역별로 for문을 수행하여 인스턴스 id를 가져오는 것도 가능합니다.
어떤 인스턴스에서 ME가 예정되어 있는지 파악했다면 이 정보를 가지고 필요에 따라 작업을 수행하면 됩니다. 이번 글에서는 Kafka 프로세스와 주키퍼 프로세스가 실행되고 있는 인스턴스를 예로 들어 ME 대응 조치를 수행하겠습니다. 다음 단계부터는 ansible 플레이북을 사용해보겠습니다.
2. 인스턴스에서 실행 중인 프로세스 중지
Kafka 프로세스가 실행 중인 인스턴스를 안전하게 중지하려면 우선 Kafka 서버를 중지한 후 주키퍼 서버를 중지해줘야 합니다. 이를 위해 우리는 kafka 설치 시 bin 폴더에 함께 다운로드되는 아래의 sh 파일을 이용하겠습니다.
- kafka-server-stop.sh
- zookeeper-server-stop.sh
ansible에서는 원격 호스트에서 쉘 커맨드를 사용할 수 있는 shell 모듈을 제공합니다. 위의 sh 파일을 ansible 플레이북으로 실행하기 위해 아래와 같이 작성합니다.
- name: "stop kafka"
shell: |
~/kafka_X.X.X/bin/kafka-server-stop.sh
- name: "stop zookeeper"
shell: |
~/kafka_X.X.X/bin/zookeeper-server-stop.sh
3. 인스턴스 재부팅
클라우드 환경에서는 ME 예정인 인스턴스를 재부팅하면 해당 이벤트가 해소되면서 같은 사양의 새 인스턴스로 자동 교체됩니다. 2번 단계에서 Kafka 프로세를 안전하게 중지했으니 인스턴스 재부팅을 하도록 하겠습니다.
인스턴스 재부팅 작업은 ansible에서 제공하는 amazon.aws.ec2 모듈을 이용해보겠습니다.
- name: 'reboot instance'
delegate_to: localhost
amazon.aws.ec2:
region: 'ap-northeast-2'
instance_tags:
Name: 'i-xxxxxxxxxxxx'
state: restarted
위에서 보시면 알 수 있듯이 이번 작업을 수행하는 서버는 ME 인스턴스가 아닌 ‘localhost’입니다. 따라서 로컬호스트에 aws 키 등 aws 자원을 컨트롤할 수 있는 설정이 되어 있어야겠죠. amazon.aws.ec2 모듈은 플레이북에 특별히 지정하지 않아도 aws configure 파일이나 환경변수 등을 스스로 조회하여 키 값을 가져옵니다.
4. 인스턴스에서 실행할 프로세스 시작
2번 단계에서 중지한 Kafka 프로세스를 ansible 플레이북을 이용하여 다시 시작해보겠습니다. Kafka 서버와 주키퍼 서버를 시작하는 스크립트는 아래와 같으며, bin 폴더에서 확인할 수 있습니다.
- kafka-server-start.sh
- zookeeper-server-start.sh
서버를 시작할 때도 ansible 기본 모듈인 shell 모듈을 사용해보겠습니다. 위의 sh 파일을 ansible 플레이북으로 실행하기 위해 아래와 같이 작성합니다.
- name: "start zookeeper"
shell: |
nohup ~/kafka_X.X.X/bin/zookeeper-server-start.sh -daemon config/zookeeper.properties
- name: "start kafka"
shell: |
nohup ~/kafka_X.X.X/bin/kafka-server-start.sh -daemon config/server.properties
2번과 다르게 Kafka 서버를 다시 시작할 때는 주키퍼 서버부터 시작해줘야 합니다.
여기에서 한 가지 고려할 것이 있는데, 바로 해당 인스턴스가 접속 가능한 상태가 될 때까지 기다려야 합니다. 3번 단계에서 인스턴스를 재부팅 했기 때문이죠. 그럼 어떻게 해야 인스턴스가 접속이 가능할 때까지 기다리게 할 수 있을까요? 이 기능도 아래와 같이 ansible의 기본 모듈인 ‘wait_for_connection’을 이용하면 쉽게 구현할 수 있습니다.
- name: ‘wait for connection’
wait_for_connection:
delay: 60
timeout: 300
마치며
이제 상기의 코드블록들 잘 연결하기만 하면 EC2 ME 자동화 조치를 완료할 수 있겠군요. 얼마 전 이 기능을 직접 개발할 때는 ME 정보를 어떻게 가져올지, 스크립트는 어떤 식으로 실행할지 등에 대해 많은 고민을 했었는데, 글로 적고 보니 꽤 간단하고 쉬워 보이는군요. 오늘 이 글이 마침 비슷한 기능을 개발하고 있는 분들에게 좋은 참고자료가 되었으면 좋겠습니다.