Skip to main content

Command Palette

Search for a command to run...

EC2 Docker container logs to CloudWatch

Updated
5 min read
EC2 Docker container logs to CloudWatch

사이드 프로젝트나 회사에서 가끔 EC2 에 간단한 어플리케이션을 뛰우기 위해 도커 Container 를 통해 서버를 뛰울때가 있다. 이럴때면 항상 문제가 되는 점이 이 Container 에 대한 log 를 쉽게 확인할 수 없다는 점이다. 따라서 이를 logStream 을 통해 cloudWatch 로 보내거나 다른 log 서비스로 보내야 한다. 오늘은 Docker container 에서 아주 쉽게 CloudWatch 로 보내는 방법을 작성해보려고 한다.

들어가기에 앞서

사실 여기서 CloudWatch Log driver 를 설정하는게 좋은데, 아무래도 이 글을 방문한 사람들은 대다수 logging driver 를 configure 하는데 익숙하지 않을 것 이라고 생각하기 때문에 조금 raw 한 방식으로 가보려고 한다. 만약 자신이 Docker 를 공부해보고 좀 알아가보고 싶다면 logging driver 쪽을 공부해가며 적용해보길 바란다.

Docker logs

기본적으로 Docker 는 log 를 남기도록 되어있다. 이건 logging driver 를 어떻게 설정하냐에 따라 달라질 수 있는데, 기본적으로는 JSON File logging driver 를 설정하도록 되어있다. 해당 driver 는 docker logs 를 아래와 같이 json 형태로 남겨준다. 혹시 모르는 사람이 있을 수도 있으니 로그를 10줄 확인하는 커맨드를 함께 작성해보자.

docker ps -a

>>> CONTAINER ID   IMAGE                                                           COMMAND                  CREATED      STATUS      PORTS                                                                                  NAMES
6396bb7d8e98   example-container   "docker-entrypoint.s…"   4 days ago   Up 4 days   0.0.0.0:3000->3000/tcp, :::3000->3000/tcp, 0.0.0.0:6543->6543/tcp, :::6543->6543/tcp   elegant_kapitsa

보통 container id 기반으로 docker log 를 조회하므로 열줄만 확인하기 위해서는 아래처럼 커맨드를 작성하면 된다.

docker logs --tail 10 6396bb7d8e98

2025-05-25 13:07:00 [info] [1UQ9FYDqJEjCEcw2FmYe2] start notification batch 
2025-05-25 13:07:00 [info] [WXNxA-WZMSu5srV_BWdGp] no notification query to send 
2025-05-25 13:08:00 [info] [VzPfGxoI0jdWIba-IfH5K] start notification batch 
2025-05-25 13:08:00 [info] [QBu5oW6J2z1fbCZx6FfQI] no notification query to send 
2025-05-25 13:09:00 [info] [1Aso2fvtff4HcPvUO2Hg5] start notification batch 
2025-05-25 13:09:00 [info] [I_kbpV0kylgdt0pxdjDBQ] no notification query to send 
2025-05-25 13:10:00 [info] [8OuDbyiy-TPnvSHP2WEzz] start notification batch 
2025-05-25 13:10:00 [info] [eOHYte-dKFiUaeDKptlGk] start notification batch 
2025-05-25 13:10:00 [info] [Lg0JMayauwq5OZ9I9gQ4u] no notification query to send 
2025-05-25 13:10:00 [info] [tuunzsY20t8xosT_5WnI9] no notification query to send

그러면 위와 같이 현재 어플리케이션의 로그가 잘 찍히는 것을 확인해볼수 있다. 이제 해당 docker container 가 로그를 어디에 남기는지를 확인해야 하는데, 이건 docker 명령어 중 하나인 docker inspect 로 확인가능하다. 공식문서에도 친절히 도커 컨테이너 로그 Path 찾는법이 나와있으니 항상 공식문서를 잘 확인해보자.

docker inspect --format='{{.LogPath}}' 6396bb7d8e98

/var/lib/docker/containers/6396bb7d8e9816cec19a19fa6d01ce66f44b4b122c8c3c03aa45c422710796e0/6396bb7d8e9816cec19a19fa6d01ce66f44b4b122c8c3c03aa45c422710796e0-json.log

우리의 컨테이너 id 를 넘겨주게 되면 위와 같이 docker log 가 위치한 경로(Path) 가 출력되게 된다. 아마도 오랫동안 열려있었다면 상당히 클 것이므로 Linux 에서 지원해주는 tail 명령어를 사용해보도록 하자. 일단 tail 이 뭔지 모르는 사람이 있을수도 있으니 tail —help 를 통해 어떤 역할을 해주는지 좀 보자

Print the last 10 lines of each FILE to standard output.
With more than one FILE, precede each with a header giving the file name.

With no FILE, or when FILE is -, read standard input.

Mandatory arguments to long options are mandatory for short options too.
  -c, --bytes=[+]NUM       output the last NUM bytes; or use -c +NUM to
                             output starting with byte NUM of each file
  -f, --follow[={name|descriptor}]
                           output appended data as the file grows;
                             an absent option argument means 'descriptor'
  -F                       same as --follow=name --retry
  -n, --lines=[+]NUM       output the last NUM lines, instead of the last 10;
                             or use -n +NUM to output starting with line NUM
      --max-unchanged-stats=N
                           with --follow=name, reopen a FILE which has not
                             changed size after N (default 5) iterations
                             to see if it has been unlinked or renamed
                             (this is the usual case of rotated log files);
                             with inotify, this option is rarely useful
      --pid=PID            with -f, terminate after process ID, PID dies

대충 읽어보면 STDOUT(standard output) 의 마지막 10번째 줄을 출력해준다고 한다. 모든 로그 파일을 열게 되면 상당히 길어지고, 부담이 될수 있으므로 우리는 이 tail 을 사용해서 마지막 10번째 줄만 확인해볼 것이다.

tail /var/lib/docker/containers/6396bb7d8e9816cec19a19fa6d01ce66f44b4b122c8c3c03aa45c422710796e0/6396bb7d8e9816cec19a19fa6d01ce66f44b4b122c8c3c03aa45c422710796e0-json.log
[ec2-user@ip-0-0-0-0 ~]$ sudo tail /var/lib/docker/containers/6396bb7d8e9816cec19a19fa6d01ce66f44b4b122c8c3c03aa45c422710796e0/6396bb7d8e9816cec19a19fa6d01ce66f44b4b122c8c3c03aa45c422710796e0-json.log
{"log":"2025-05-25 13:17:00 [\u001b[32minfo\u001b[39m] [_nGw3N58VfZ3bSROjvS3t] start notification batch \n","stream":"stdout","time":"2025-05-25T04:17:00.002011521Z"}
{"log":"2025-05-25 13:17:00 [\u001b[32minfo\u001b[39m] [efRuI9tyoFaqP5Tzkc9VJ] no notification query to send \n","stream":"stdout","time":"2025-05-25T04:17:00.029296098Z"}
{"log":"2025-05-25 13:18:00 [\u001b[32minfo\u001b[39m] [5CTslcZhX7PlEaMg5gJ5d] start notification batch \n","stream":"stdout","time":"2025-05-25T04:18:00.002467655Z"}
{"log":"2025-05-25 13:18:00 [\u001b[32minfo\u001b[39m] [YJ5IPVhbxbBdWkG5u-lYC] no notification query to send \n","stream":"stdout","time":"2025-05-25T04:18:00.034712075Z"}
{"log":"2025-05-25 13:19:00 [\u001b[32minfo\u001b[39m] [Knbdfcav4bvY7ISA5CFHq] start notification batch \n","stream":"stdout","time":"2025-05-25T04:19:00.001702587Z"}
{"log":"2025-05-25 13:19:00 [\u001b[32minfo\u001b[39m] [yPOFrwgGJYrzBoMptCvD2] no notification query to send \n","stream":"stdout","time":"2025-05-25T04:19:00.03466593Z"}
{"log":"2025-05-25 13:20:00 [\u001b[32minfo\u001b[39m] [mY9a7bqB2ui9ikC-dxw6o] start notification batch \n","stream":"stdout","time":"2025-05-25T04:20:00.002331836Z"}
{"log":"2025-05-25 13:20:00 [\u001b[32minfo\u001b[39m] [wkUPGnu2nBAW-5BnRN5iV] start notification batch \n","stream":"stdout","time":"2025-05-25T04:20:00.004497565Z"}
{"log":"2025-05-25 13:20:00 [\u001b[32minfo\u001b[39m] [Me84S199I61hQFKCuklRQ] no notification query to send \n","stream":"stdout","time":"2025-05-25T04:20:00.035242873Z"}
{"log":"2025-05-25 13:20:00 [\u001b[32minfo\u001b[39m] [9ERMLXHUyd3h-qhjKblqg] no notification query to send \n","stream":"stdout","time":"2025-05-25T04:20:00.035332976Z"}

위와 같이 Log 가 json 형태로 남겨져 있는 것을 확인할 수 있다. 이제 어떻게 남겨져 있는지 까지 확인해 봤으니 이걸 CloudWatch 로 연결해보도록 하자.

Connect to Cloud watch

AWS 에서는 기본적으로 아마존 서비스 내에서도 특정 서비스에서 다른 서비스로 특별한 행동을 취할때 권한이 필요하다. 예를 들면, 우리가 EC2 서비스에서 CloudWatch 라는 서비스로 로그를 보내기 위해서는 EC2 서비스가 CloudWatch 에 대한 행동을 할 수 있는 권한이 필요하다. 그것을 위해 보통 IAM 을 이용한다.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "*"
    }
  ]
}

따라서 IAM 을 위와 같이 만들고 EC2의 Modify IAM role 을 이용해서 추가해주도록 한다. 이제 EC2 가 CloudWatch 에 대해 로그 그룹, 로그 스트림을 만들수 있고, 로그 이벤트를 발송할수도 있는 최소한의 권한을 가지게 된다.

JSON 설정

{
  "logs": {
    "logs_collected": {
      "files": {
        "collect_list": [
          {
            "file_path": "/var/lib/docker/containers/6396bb7d8e9816cec19a19fa6d01ce66f44b4b122c8c3c03aa45c422710796e0/6396bb7d8e9816cec19a19fa6d01ce66f44b4b122c8c3c03aa45c422710796e0-json.log",
            "log_group_name": "docker-messaging-server",
            "log_stream_name": "{instance_id}",
            "timestamp_format": "%Y-%m-%dT%H:%M:%S.%fZ",
            "timezone": "UTC"
          }
        ]
      }
    }
  }
}

이제 json 파일을 통해 설정을 진행해야 한다. 일단 가볍게 위의 json file 을 살펴보면 크게 아래와 같은 속성이 존재한다. (해당 json 파일을 vi or vim 을 통해 /opt/aws/amazon-cloudwatch-agent/bin/config.json 에 만들어주면 된다)

  • file_path: 로그가 놓여진 경로(이전에 docker inspect 로 확인했던 경로)

  • log_group_name: 로그가 그룹핑 될 그룹

  • log_stream_name: 로그 그룹안에서 우리가 전송할 로그가 전송될 스트림(알아보기 쉽게하기 위해 instance_id 를 이용하도록 했다)

위 처럼 속성파일을 적어주면 이제 대략적인 준비는 끝났다. 이제 CloudWatch Agent 를 실행시켜서 해당 config 정보를 이용해서 로그를 CloudWatch 로 보내도록 하자.

sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl \
  -a fetch-config \
  -m ec2 \
  -c file:/opt/aws/amazon-cloudwatch-agent/bin/config.json \
  -s

가볍게 설명하면 fetch_config 라는 action 을 하는데 mode 는 ec2 모드이고, config 파일 경로는 /opt/aws/amazon-cloudwatch-agent/bin/config.json 에 존재한다는 뜻이다. 이제 커맨드가 입력되고 잘 되었다면 Error 가 보이지않고, Success 만 보일것이다.

검증

CloudWatch 에 로그가 늦게 들어오는건지 이게 실패한건지 헷갈릴때가 있을 것이다. 따라서 아래 커맨드를 입력해서 잘 가고 있는지 확인해보자. 보통 잘 안가고 있다면 어떤 에러가 해당 로그에 존재하고 있을 것이다.

[ec2-user@ip-0-0-0-0 ~]$ sudo tail -f /opt/aws/amazon-cloudwatch-agent/logs/amazon-cloudwatch-agent.log
2025-05-25T03:45:22Z I! [inputs.logfile] turned on logs plugin
2025-05-25T03:45:22Z I! {"caller":"service@v0.115.0/service.go:166","msg":"Setting up own telemetry..."}
2025-05-25T03:45:22Z I! {"caller":"service@v0.115.0/service.go:215","msg":"Skipped telemetry setup."}
2025-05-25T03:45:22Z I! {"caller":"service@v0.115.0/service.go:238","msg":"Starting CWAgent...","Version":"1.300054.1","NumCPU":2}
2025-05-25T03:45:22Z I! {"caller":"extensions/extensions.go:39","msg":"Starting extensions..."}
2025-05-25T03:45:22Z I! {"caller":"extensions/extensions.go:42","msg":"Extension is starting...","kind":"extension","name":"entitystore"}

마치며

이제 cloud watch 의 docker-messaging-server 라는 log group 하위에 인스턴스 id 로 로그 스트림이 생성된것을 확인해볼수 있다. 조금 더 견고하게 로그를 관리하기 위해서는 log 가 너무 많이 쌓일 수 있으므로 retention 등등을 고려해보는 것도 좋은 공부가 될 것이다. 항상 공식문서를 잘 읽고 적용해보는 습관을 길러보자.

More from this blog

RDB 에서 큰 컬럼을 인덱스로 잡으면 안되는 이유

B-Tree 는 기본적으로 페이지 사이즈 와 저장할 수 있는 원소의 개수를 고정값으로 사용한다. 하지만 우리가 실제로 페이지에 저장하는 값은 가변적인 크기를 가지고 있기 때문에 필연적으로 물리적으로 저장해야할 개수가 다 차기도 전에 페이지가 넘치는 상황에 부딪히게 된다. 예를 들어 100KB 를 저장하는 페이지에 위와 같이 데이터를 저장한 상태이다. 여

Feb 26, 20262 min read49

Slotted Page

데이터베이스와 관련된 기술을 보다보면 어떻게 데이터를 관리하고 저장하지? 특히 단편화(Fragmentation) 이 일어나는 것을 어떻게 통제하고 관리할까? 혹은 정렬된 자료구조 내부에서 데이터의 순서를 보존하기 위해 어떠한 행위들을 할까? 궁금해집니다. 오늘은 조금 더 데이터베이스 내부에 쓰이는 자료구조를 들여다보며 연관된 행위를 공부해보려고 합니다. F

Feb 22, 20264 min read63
Slotted Page

MCP 를 통한 workflow 자동화

AI native 최근에 LinkedIn 이나 여러 소셜 플랫폼들의 글을 보면 AI native 회사 라는 워딩들이 많이 보입니다. IBM 의 정의에 따르면 AI native 를 아래와 같이 정의한다고 하는데요. “AI를 사고와 업무 방식에 끊임없이 내재화하는 상태” 그렇다면 팀원들이 계속해서 AI 를 사고와 업무 방식에 끊임 없이 내재화 하려면 어떻게 해야할까요? 개발자들은 이미 Claude code 나 Codex 등 여러 AI Tool...

Feb 14, 20263 min read100

파이썬 톺아보기 2화 - Ast 와 바이트코드

식(Expression) 과 문장(Statement) 프로그래밍을 공부하다보면 위 두 단어를 반드시 마주하게 된다. 가끔 헷갈려하는 경우가 많은데 오늘은 python 에서 기본 모듈인 ast 모듈을 공부하며 이를 알아보도록 하자. 식(Expression) 기본적으로 식(Expression) 이란 평가되면 값이 나오는 코드 조각을 뜻한다. 파이썬에서는 어떠한 부분들이 있을까? 노드 타입설명예시 BinOp이항 연산a + b, x * y...

Feb 6, 20267 min read30
D

dev_roach

41 posts