티스토리 뷰

드디어 개인프로젝트를 배포하고 Github Action으로 배포 자동화까지 완료했습니다.

운영환경에서 테스트를 하는데 서버의 에러로그를 확인하는 방법이 필요했습니다.

로그를 확인할 방법이 따로 없다 보니 디버깅이 어렵고 ec2에 직접 접속해서 docker compose up을 해서 로그를 확인해 보는 수밖에 없었습니다.

또한 동일한 ec2에 프론트엔드, 백엔드 도커 컨테이너를 동시에 실행하다 보니 메모리나 cpu가 잘 버텨줄지도 궁금했습니다.

Nextjs가 이미지최적화를 하는데 꽤 많은 cpu를 소모한다는것을 알고 있었거든요.

 

서버의 log확인, cpu, 메모리 상태 모니터링을 해야할 필요가 있었고 aws 서비스와 연동이 좋은 cloudwatch를 사용해 보기로 했습니다.

방법은 어렵지 않습니다. 블로그 하단에 첨부한 참고자료만 보시더라도 충분히 하실 수 있습니다.

타 블로그에는 없지만 제가 직접 적용 과정에서 겪은 트러블슈팅을 남기겠습니다.

 

cloudwatch agent 를 설치마법사를 통해 설정하면 다음과 같은 질문이 나옵니다.

Do you want to monitor any log files?

1. yes

2. no

default choice: [1]:

어떤 로그 파일들을 모니터링 하고싶냐고 묻는 것인데, '로그를 남기려면 로그를 저장한 파일이 있어야 되는구나'라고 유추할 수 있습니다. 그럼 콘솔에만 찍히던 로그를 어떻게 저장해야 할까요?

검색해 보니 winston이라는 라이브러리를 사용하면 파일시스템을 사용해서 로그를 특정 폴더에 저장할 수 있다고 합니다.

참고자료

일단 winston을 사용해서 로컬에 로그 파일을 남겨보는 것부터 테스트해보겠습니다.

// winston.config.ts
import {
  utilities as nestWinstonModuleUtilities,
  WinstonModule,
} from 'nest-winston';
import * as winstonDaily from 'winston-daily-rotate-file';
import * as winston from 'winston';

const isProduction = process.env['NODE_ENV'] === 'production';
const logDir = __dirname + '/../../logs';

const dailyOptions = (level: string) => {
  return {
    level,
    datePattern: 'YYYY-MM-DD',
    dirname: logDir + `/${level}`,
    filename: `%DATE%.${level}.log`,
    zippedArchive: true,
    maxSize: '10m',
    maxFiles: '14d',
  };
};

export const winstonLogger = WinstonModule.createLogger({
  transports: [
    new winston.transports.Console({
      level: isProduction ? 'info' : 'silly',
      format: isProduction
        ? winston.format.simple()
        : winston.format.combine(
            winston.format.timestamp(),
            winston.format.ms(),
            nestWinstonModuleUtilities.format.nestLike('MyApp', {
              colors: true,
              prettyPrint: true,
            }),
          ),
    }),
    new winstonDaily(dailyOptions('info')),
    new winstonDaily(dailyOptions('warn')),
    new winstonDaily(dailyOptions('error')),
  ],
});

config.ts를 작성하고 

// main.ts
async function bootstrap() {
  const app = await NestFactory.create(AppModule, {
    logger: winstonLogger,
  });

로거를 winstonLogger로 대체합니다.

// logger.middleware.ts
import {
  Inject,
  Injectable,
  Logger,
  LoggerService,
  NestMiddleware,
} from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  constructor(@Inject(Logger) private readonly logger: LoggerService) {}

  use(req: Request, res: Response, next: NextFunction) {
    // 요청 객체로부터 ip, http method, url, user agent를 받아온 후
    const { ip, method, originalUrl } = req;
    const userAgent = req.get('user-agent');

    res.on('finish', () => {
      const { statusCode } = res;
      this.logger.log(
        `${method} ${originalUrl} ${statusCode} ${ip} ${userAgent}`,
      );
    });

    next();
  }
}

로거 미들웨어를 만들고

// app.module.ts
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(LoggerMiddleware).forRoutes('*');
  }
}

app모듈에 적용해 줬습니다.

 

이렇게 하면 프로젝트 루트에 logs폴더가 저절로 생기는 것을 확인할 수 있습니다.

자 이제 로컬에서 logs폴더가 생기는 것을 확인했으니 docker 이미지를 다시 운영에 배포해 주겠습니다.

하지만 ec2 인스턴스에는 컨테이너가 실행돼도 logs폴더가 생기지 않습니다. 

이유는 db를 인스턴스의 특정 폴더에 생성해 주듯이 logs 폴더도 인스턴스의 특정폴더에 연결해 줘야 생성됩니다.

이는 당연하게도 도커 컨테이너로 실행했기 때문에 도커 컨테이너 내부에서 logs폴더가 생성되는 것입니다.

이것을 호스트의 폴더에 연결해 줄 필요가 있습니다. 그래야 도커컨테이너가 재실행되거나 새로운 이미지로 바뀌더라도 로그를 유지할 수 있습니다. 이것을 볼륨마운트라고 합니다.

그럼 볼륨마운트 설정도 하겠습니다.

version: '3.8'
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - xxxx:xxxx
    env_file:
      - .env
    networks:
      - babylon-api-net
    volumes:
      - ./logs:/usr/src/app/logs
    depends_on:
      - postgres

volumns로 설정이 가능합니다

./logs : /usr/src/app/logs 는 현재 docker-compose가 실행되는 폴더의 logs폴더를 docker container의 /use/src/app/logs와 맞추겠다! 뭐 이런 뜻인 것 같습니다. 

확인해 보시려면 명령어로

docker exec -it <컨테이너 id 또는 name> ls /usr/src/app/logs를 해보시면 됩니다.

이렇게 컨테이너 내부의 해당 path를 찍어보시면 로컬에서 만들었던 것처럼 error, warn, info 폴더가 들어있는 것을 보실 수 있습니다.

즉, 일단 도커컨테이너에 logs 폴더가 있는 것을 확인했으면 1차 성공이고

볼륨마운트 설정한 뒤에 ec2인스턴스 docker-compose.yaml 파일이 위치한 폴더에 logs 폴더가 잘 생성되면 2차 성공입니다.

저는 이 부분에서 트러블이 좀 있었는데... 로컬에 있는 docker-compose.yaml만 수정하고 실제 ec2인스턴스에서 컨테이너를 실행시켜 주는 docker-compose.yaml은 그대로 놔둔 채로 왜 안될까... 하며 고민했습니다. ec2 인스턴스에서 컨테이너를 실행하는 것은 인스턴스의 docker-compose.yaml이니까 그 파일을 수정해야 됩니다.. 

이제 호스트 서버에 logs폴더를 확인했으면 드디어 cloudwatch를 연결하면 됩니다.

일단 ec2가 cloudwatch에 접근할 수 있도록 보안에 IAM역할로 CloudWatchAgentServerPolicy 를 부여해주세요

그리고 ec2에서 cloudwatch agent를 설치합니다.

 

Agent 설치

wget https://s3.amazonaws.com/amazoncloudwatch-agent/ubuntu/amd64/latest/amazon-cloudwatch-agent.deb
sudo dpkg -i -E ./amazon-cloudwatch-agent.deb

 

마법사로 설정세팅

sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-config-wizard

 

On which OS are you planning to use the agent?
1. linux
2. windows
3. darwin
default choice: [1]:
1

Trying to fetch the default region based on ec2 metadata...
Are you using EC2 or On-Premises hosts?
1. EC2
2. On-Premises
default choice: [1]:
1

Which user are you planning to run the agent?
1. root
2. cwagent
3. others
default choice: [1]:
1

Do you want to turn on StatsD daemon?
1. yes
2. no
default choice: [1]:
2

Do you want to monitor metrics from CollectD? WARNING: CollectD must be installed or the Agent will fail to start
1. yes
2. no
default choice: [1]:
2

Do you want to monitor any host metrics? e.g. CPU, memory, etc.
1. yes
2. no
default choice: [1]:
1

Do you want to monitor cpu metrics per core?
1. yes
2. no
default choice: [1]:
1

Do you want to add ec2 dimensions (ImageId, InstanceId, InstanceType, AutoScalingGroupName) into all of your metrics if the info is available?
1. yes
2. no
default choice: [1]:
1

Do you want to aggregate ec2 dimensions (InstanceId)?
1. yes
2. no
default choice: [1]:
1

Would you like to collect your metrics at high resolution (sub-minute resolution)? This enables sub-minute resolution for all metrics, but you can customize for specific metrics in the output json file.
1. 1s
2. 10s
3. 30s
4. 60s
default choice: [4]:
4

Which default metrics config do you want?
1. Basic
2. Standard
3. Advanced
4. None
default choice: [1]:
2

Are you satisfied with the above config? Note: it can be manually customized after the wizard completes to add additional items.
1. yes
2. no
default choice: [1]:
1

Do you have any existing CloudWatch Log Agent (http://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/AgentReference.html) configuration file to import for migration?
1. yes
2. no
default choice: [2]:
2

Do you want to monitor any log files?
1. yes
2. no
default choice: [1]:
2

Do you want the CloudWatch agent to also retrieve X-ray traces?
1. yes
2. no
2

The config file is also located at /opt/aws/amazon-cloudwatch-agent/bin/config.json.
Edit it manually if needed.
Do you want to store the config in the SSM parameter store?
1. yes
2. no
default choice: [1]:
2

 

저는 위와같이 설정했는데, 잘못 작성해도 괜찮습니다.

나중에 config.json 파일을 수동으로 수정해도 됩니다.

agent 사용자를 root로 했는데, 처음에는 cwagent로 설정했었습니다.

근데 특정 매트릭을 수집할때 docker관련 파일에 접근하려고 할때 cwagent에는 권한이 없어서 에러가 발생했습니다.

그래서 root사용자가 이것을 실행하도록 해서 에러를 해결했고 로그, 매트릭이 수집되도록 했습니다.

그리고 로그와 관련된 내용에 no를 설정했는데, 이부분은 config.json에 직접 작성했습니다.

아래와 같습니다.

"logs": {
                "logs_collected": {
                        "files": {
                                "collect_list": [
                                        {
                                        "file_path": "/home/ubuntu/app/logs/error/*.error.log",
                                        "log_group_name": "Xxxxxxx",
                                        "log_stream_name": "xxxxxxxx"
                                        },
                                        {
                                        "file_path": "/home/ubuntu/app/logs/warn/*.warn.log",
                                        "log_group_name": "xxxxxxxxx",
                                        "log_stream_name": "xxxxxxxxx"
                                        },
                                        {
                                        "file_path": "/home/ubuntu/app/logs/info/*.info.log",
                                        "log_group_name": "xxxxxxxxx",
                                        "log_stream_name": "xxxxxxxxx"
                                        }
                                ]
                        }
                }

"logs"를 amazon-cloudwatch-agent/bin/config.json 에서 추가하면 됩니다.

저의 경우에는 파일을 날짜별로 나눠서 <오늘날짜>.error.log, <오늘날짜>.warn.log 이런식으로 파일을 만들었기 때문에 *을 사용해서 모든 파일을 추적하도록 했습니다. 경로는 볼륨마운트로 설정했던 logs폴더입니다. log_group_name, log_stream_name은 각자 상황에 맞게 입력해주세요.

 

agent config.json설정파일 적용

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

 

이렇게 입력하면 cloudwatch에서 매트릭과 로그를 확인할 수 있게 됩니다.

이제 백엔드 에러를 확인하기 위해 ec2에 접속하지 않아도 됩니다.

 

 

참고자료

https://dev.classmethod.jp/articles/try-installing-cloudwatch-agent-on-ec2-instances/

 

EC2 인스턴스에 CloudWatch Agent를 설치해서 메모리 사용량 확인해 보기 | DevelopersIO

EC2 인스턴스에 CloudWatch Agent를 설치해서 EC2 인스턴스 메모리 사용량을 확인하는 과정을 정리해 봤습니다.

dev.classmethod.jp

https://shg-engineer.tistory.com/12

 

EC2 인스턴스에 AWS Cloud watch Agent 설치하기

AWS Cloud watch에서는 인스턴스나 기타 서비스에 대한 성능 지표들을 확인할 수 있다. 하지만 인스턴스 내부 시스템 지표는 수집할 수가 없다. 따라서 이러한 지표를 확인하기 위해 외부 솔루션(data

shg-engineer.tistory.com

https://velog.io/@iamtaehoon/Cloudwatch%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%9C-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A7%81-EC2-Ubuntu

 

Cloudwatch를 사용한 메모리 모니터링 - EC2, Ubuntu

AWS Ubuntu 환경에서 메모리를 모니터링하기 위한 도구

velog.io

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG more
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
글 보관함