aws spot instance 서버로그 관리(feat. fluentd)
Spot인스턴스를 사용하면 인스턴스 요금을 최대한 줄일 수 있습니다만 spot인스턴스 사용할 수 있는 용량이 적어지거나 설정한 요금보다 비싸지게 되면 spot인스턴스가 Stop되거나 terminate되는 경우가 있습니다.
terminate될 경우에 spot인스턴스내에 있던 로그를 영영 복구할 수 없게 되죠.
그래서 로그를 어떻게 하면 실시간으로 한곳에 모으거나 spot인스턴스가 terminate되는 신호를 trigger하여 로그를 Flush할 수 없을까하는 방법을 찾던 중에 fluentd를 찾게 되었습니다.
fluentd란?
그럼 fluentd는 무엇일까
간단히 얘기하자면 fluentd는 로그를 수집하는 도구입니다. 그리고 Opensource입니다.
Fluentd | Open Source Data Collector
"Logs are streams, not files. I love that Fluentd puts this concept front-and-center, with a developer-friendly approach for distributed systems logging." Adam Wiggins, Heroku co-founder
www.fluentd.org
fluentd(td-agent)를 사용하면 로그를 한곳으로 수집할 수 있고 또는 S3 bucket에 실시간으로 로그를 전송할 수 있습니다.
현재 SRE팀으로 시스템 운영을 하는 회사에서는 기존 시스템 로그, 애플리케이션 로그를 S3 bucket으로 S3 Sync를 이용하여 전송하고 있는데 cron설정으로 1시간마다 S3 sync하고 있어서 Spot인스턴스가 terminate하여 로그가 복구가 안되는 경우가 항상 있었습니다. 또한 S3 sync의 단점으로 뽑자면 로그파일의 용량이 어마무지하게 커지면 S3 Sync로 전송하는 경우에 리소스를 엄청 잡아 먹게 되어 서버에 부하를 주게 됩니다. 로그를 수집하려고 웹서비스에 영향을 주는 것은 잘못된 설계라고 할 수 있죠.
S3 Sync CLI
aws s3 sync ${APPLOGPATH} s3://${BUCKET_NAME/${APP_NAME}/${APP_ENV}/${year}/${month}/${day}/ --exclude "*" --include "${ymd}_*.log"
fluentd구성
그래서 fluentd로 할 수 있는 방법으로 재설계하게 되었습니다.
다음과 같이 간단하게 설계했습니다.
autoscaling group을 ondemand와 spot으로 구성하여 Scale Out 또는 Scale In 시에는 Spot인스턴스만 늘어나고 줄어 들게끔 구성하였습니다.
1. 통상적으로 운영시에는 로그파일의 용량이 커짐에 따라 수집할 데이터가 많아지므로 fluentd의 설정을 60초마다 tail방식으로 로그를 S3에 전송하도록 했습니다.
2. Scale In하게 되어 인스턴스가 줄어들게 되거나 spot인스턴스의 가용 용량이 적어지게 되거나 설정한 요금보다 높아지게 되면 spot인스턴스가 자동적으로 terminate하게 될 경우에는 Buffer상에 쌓아둔 로그를 자동적으로 flush하도록 스크립트를 작성하였습니다. Spot인스턴스는 중단되기 2분전에 중단 이벤트가 발생하므로 중단 이벤트를 트리거로 스크립트가 실행되게끔 설정하였습니다.
spot 인스턴스 중단 이벤트는 termination-time 메타데이터를 활용하는 것이 좋습니다.
TOKEN=`curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"`
if curl -H "X-aws-ec2-metadata-token: $TOKEN" -s http://169.254.169.254/latest/meta-data/spot/termination-time | grep -q .*T.*Z; then echo terminated; fi
참고 : https://docs.aws.amazon.com/ko_kr/AWSEC2/latest/UserGuide/spot-interruptions.html
스팟 인스턴스 중단 - Amazon Elastic Compute Cloud
스팟 인스턴스 중단 스팟 인스턴스에 대한 수요는 매 순간 상당히 다를 수 있으며 스팟 인스턴스의 가용성도 사용 가능한 미사용 EC2 인스턴스의 양에 따라 상당히 달라질 수 있습니다. 스팟 인스턴스가 중단될 가능성은 항상 있습니다. 따라서 스팟 인스턴스 중단에 대비하여 애플리케이션을 준비해야 합니다. EC2 집합 또는 스팟 집합에 지정된 온디맨드 인스턴스는 중단할 수 없습니다. 중단 이유 Amazon EC2에서 스팟 인스턴스를 중단시킬 수 있는 이유는 다음
docs.aws.amazon.com
중단 이벤트뿐만 아니라 Spot인스턴스가 정지될 경우를 대비하여 buffer flush 스크립트를 daemon으로 설정할 필요가 있습니다. 자세한 내용을 제일 아래에 쓰도록 하겠습니다.
Fluentd인스톨
AmazonLinux (CentOS6)
$ curl -L https://toolbelt.treasuredata.com/sh/install-redhat-td-agent3.sh | sh
AmazonLinux2 (CentOS7)
# Amazon Linux 2
$ curl -L https://toolbelt.treasuredata.com/sh/install-amazon2-td-agent3.sh | sh
※fluentd공식document : https://docs.fluentd.org/installation/install-by-rpm
Install by RPM Package (Redhat Linux)
docs.fluentd.org
td-agent.conf 설정
<source>
@type tail
format /^(?<body>.*)$/
path /var/log/httpd/*access_log,/var/log/httpd/*error_log
pos_file /var/tmp/fluentd.tail_httpd.pos
tag tail.httpd.*
refresh_interval 60
read_from_head true
</source>
<source>
@type tail
format /^(?<body>.*)$/
path /var/log/messages,/var/log/secure
pos_file /var/tmp/fluentd.tail_sys.pos
tag tail.sys.*
refresh_interval 60
read_from_head true
</source>
<source>
@type tail
format /^(?<body>.*)$/
path /usr/app/logs/%Y%m%d_*debug.log,/usr/app/logs/%Y%m%d_*error.log
pos_file /var/tmp/fluentd.tail_app.pos
tag tail.app.*
refresh_interval 60
read_from_head true
</source>
## httpd logs
<match tail.httpd.**>
@type s3
s3_region ap-northeast-2
s3_bucket system-logs
path httpd/${tag[5]}/
time_slice_format %Y/%m/%d
store_as text
s3_object_key_format "%{path}%{time_slice}/${tag[5]}.#{Socket.gethostname}.%{index}.%{file_extension}"
<buffer tag,time>
@type file
path /var/log/td-agent/buffer/httpd
timekey 60
timekey_wait 0
flush_mode interval
flush_interval 60s
flush_at_shutdown true
</buffer>
</match>
## sys logs
<match tail.sys.**>
@type s3
s3_region ap-northeast-2
s3_bucket system-logs
path syslog/${tag[4]}/
time_slice_format %Y/%m/%d
store_as text
s3_object_key_format "%{path}%{time_slice}/${tag[4]}.#{Socket.gethostname}.%{index}.%{file_extension}"
<buffer tag,time>
@type file
path /var/log/td-agent/buffer/syslog
timekey 60
timekey_wait 0
flush_mode interval
flush_interval 60s
flush_at_shutdown true
</buffer>
</match>
## application logs
<match tail.app.usr.app.**>
@type s3
s3_region ap-northeast-2
s3_bucket app-logs
path app/${tag[4]}/
time_slice_format %Y/%m/%d
store_as text
s3_object_key_format "%{path}%{time_slice}/${tag[10]}.#{Socket.gethostname}.%{index}.%{file_extension}"
<buffer tag,time>
@type file
path /var/log/td-agent/buffer/application
timekey 60
timekey_wait 0
flush_mode interval
flush_interval 60s
flush_at_shutdown true
</buffer>
</match>
※td-agent.conf관련 설정 공식 document
https://docs.fluentd.org/configuration
Configuration
docs.fluentd.org
td-agent start
AmazonLinux (CentOS6)
# td-agent daemon start
$ sudo /etc/init.d/td-agent start
# td-agent daemon 확인
$ sudo /etc/init.d/td-agent status
# td-agent stop
$ sudo /etc/init.d/td-agent stop
AmazonLinux2 (CentOS7)
# td-agent daemon start
$ sudo systemctl start td-agent.service
# td-agent daemon 확인
$ sudo systemctl status td-agent.service
# td-agent daemon stop
$ sudo systemctl stop td-agent.service
buffer flush 스크립트 daemon설정
daemon name : fluentd_bufferflush_daemon
#!/bin/bash
#
# Version: 0.1
#
# chkconfig: 2345 99 01
# description: fluentd_bufferflush_daemon shell
# processname: fluentd_bufferflush_daemon
SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
# Source function library.
. /etc/init.d/functions
lock_file="/var/lock/subsys/fluentd_bufferflush_daemon"
start() {
touch $lock_file
#spot instance buffer flush scripts execute
/bin/sh /home/ec2-user/fluentd_tool/spot_fluentd_buffer_flush.sh >> /var/log/td-agent/buffer_flush.log &
}
stop() {
rm -f $lock_file
/bin/sh /home/ec2-user/fluentd_tool/fluentd_buffer_flush.sh >> /var/log/td-agent/buffer_flush.log
}
restart() {
stop
start
}
case "$1" in
start)
start
;;
stop)
stop
;;
restart)
restart
;;
*)
echo "Usage: $0 {start|stop|restart}"
exit 1
esac
exit 0
service daemon에 등록
Amazon linux (centos6)
# service daemon 등록
cd ~/{daemon directory}/
sudo cp -r fluentd_bufferflush_daemon /etc/init.d/
cd /etc/init.d/
sudo chmod 755 fluentd_bufferflush_daemon
# service daemon 실행/정지
service fluentd_bufferflush_daemon start
service fluentd_bufferflush_daemon stop
# 실행 정지에 문제가 없으면 인스턴스 기동시에는 가장 늦게(99) 실행되도록 인스턴스 정지시에는 가장 먼저(01) 실행되도록 등록
# chkconfig: 2345 99 01
chkconfig --add fluentd_bufferflush_daemon
Amazon linux2 (centos7)
# service daemon 등록
cd ~/{daemon directory}/
sudo cp -r fluentd_bufferflush_daemon /usr/lib/systemd/system/
cd /usr/lib/systemd/system/
sudo chmod 755 fluentd_bufferflush_daemon
# service daemon 실행/정지
sudo systemctl start fluentd_bufferflush_daemon
sudo systemctl stop fluentd_bufferflush_daemon
# 실행 정지에 문제가 없으면 인스턴스 기동시에는 가장 늦게(99) 실행되도록 인스턴스 정지시에는 가장 먼저(01) 실행되도록 등록
# chkconfig: 2345 99 01
systemctl enable fluentd_bufferflush_daemon
Buffer flush실행 스크립트
fluentd_buffer_flush.sh
#!/bin/bash
now=$(date +"%Y-%m-%d %T")
echo "[INFO] START fluentd buffer flush : $now"
# run/pid의 프로세스No로 동작하는지를 확인
TD_PID_FILE=/var/run/td-agent/td-agent.pid
TD_PID=$(cat ${TD_PID_FILE})
echo "[INFO] td-agent pid : $TD_PID"
# fluentd (or td-agent) 프로세스가 SIGUSR1 를 수신하게 되면
# 강제적으로 buffer에 담아두었던 메세지(chunk)가 flush되도록 되어 있음
sudo kill -SIGUSR1 $TD_PID
now=$(date +"%Y-%m-%d %T")
echo "[INFO] END fluentd buffer flush : $now"
exit 0
spot_fluentd_buffer_flush.sh
#!/bin/bash
now=$(date +"%Y-%m-%d %T")
echo "[INFO] START Spot fluentd buffer flush : $now"
instanceId=$(curl -s http://169.254.169.254/latest/meta-data/instance-id)
echo "[INFO] instanceId: $instanceId"
echo "[INFO] termination_time: $(curl -s http://169.254.169.254/latest/meta-data/spot/termination-time)"
while :
do
if curl -s http://169.254.169.254/latest/meta-data/spot/termination-time | grep -q .*T.*Z; then
/bin/sh /home/ec2-user/fluentd_tool/fluentd_buffer_flush.sh
now=$(date +"%Y-%m-%d %T")
echo "[INFO] STOP Spot fluentd buffer flush : $now"
break
fi
sleep 5
done
exit 0
인스톨 확인
Amazonlinux (centos6)
sudo service td-agent status
grep "TD_AGENT_USER=" /etc/init.d/td-agent
grep "TD_AGENT_GROUP=" /etc/init.d/td-agent
ps -ef | grep td-agent #root로 되어 있는지 확인
grep "following tail of" /var/log/td-agent/td-agent.log
Amazonlinux (centos7)
sudo systemctl status td-agent.service
grep "TD_AGENT_USER=" /etc/init.d/td-agent
grep "TD_AGENT_GROUP=" /etc/init.d/td-agent
ps -ef | grep td-agent #root로 되어 있는지 확인
grep "following tail of" /var/log/td-agent/td-agent.log