AWS Cloudwatch를 통해 수집하는 데이터를 aws sdk를 사용하여 코드로 제어(수집, 조회, 삭제 등)할 수 있는 기능들을 정리한 글입니다. 저는 vscode에서 javascript v3버전 sdk를 사용하였습니다.

SDK는 softwore Development Kit의 약자로 AWS의 다양한 서비스 기능들을 프로그래밍을 통해 편리하게 사용하기 위한 라이브러리를 의미합니다.

 

https://aws.amazon.com/ko/sdk-for-javascript/

https://docs.aws.amazon.com/code-samples/latest/catalog/code-catalog-javascriptv3.html

 

기본적으로 sdk for javascript를 사용하려면 로컬 환경에 node js가 설치되어 있어야합니다.

https://nodejs.org/ko/download/

 

1. aws sdk 설치

vscode의 터미널에서 npm install aws-sdk 명령어를 입력하여 설치합니다.

2. 환경변수 선언하기

윈도우 서버에 cloudwatch agent를 설치할 때 자격인증을 위해 aws accesskey와 지역을 입력했는데, 이번 실습에도 이러한 절차를 거쳐야합니다.

인증 처리를 위한 두가지 방식이 있습니다.

1. 코드에서 선언

js파일 최상단에 지역, accesskey 정보를 직접 입력합니다. 정보가 노출될 위험이 있으므로 추천하진 않습니다.

1
2
3
4
var AWS = require('aws-sdk');
AWS.config.update({region:'ap-northeast-2',accessKeyId:'엑세스 키',secretAccessKey:'시크릿 엑세스 키'});
var cloudwatch = new AWS.CloudWatch();
cs

 

2. 공유 인증 자격 증명 파일 작성

C:\Users\계정이름\.aws\credentials에 계정이름과 키 정보를 입력합니다. 기본적으로 aws sdk는 해당 디렉토리 위치에 있는 credentials를 인식합니다.

주로 쓰는 기능은 putMetric, listMetric, getMetric 메소드 등인데, getMetricData만 테스트해보겠습니다.

아래는 하루 전부터 쌓아온 데이터 목록과 값을 추출하는 코드입니다.

24시간 동안의 cloudwatchagent가 5분 마다 수집한 cpu, memory, c_disk 정보를 모두 추출합니다. 코드를 읽어보시고 비슷한 포맷으로 작성하시면 됩니다.

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
var AWS = require('aws-sdk');
AWS.config.update({region:'ap-northeast-2'});
AWS.config.update({region:'ap-northeast-2',accessKeyId:'엑세스 키',secretAccessKey:'시크릿 엑세스 키'});
var cloudwatch = new AWS.CloudWatch();
 
var now = new Date();
var startTime = new Date(Date.parse(now) - 1 * 1000 * 60 * 60 * 24); //24시간전을 시작점으로
var params = {//params내부에 쿼리할 metric정보를 일일히 입력해야 함.
    EndTime: now,
    StartTime: startTime,
    MetricDataQueries: [ /* required */
      {
        Id: 'cpu_info'/* required */
        MetricStat: {
          Metric: { /* required */
            Dimensions: [
              {
                Name: 'complexcompany'/* required */
                Value: 'zz' /* required */
              },
              /* more items */
            ],
            MetricName: 'cpu_time',  
            Namespace: 'plo2'
          },
          Period: 300/* required */
          Stat: 'Average',/* required */
          Unit:'Percent'
        },
        ReturnData: true 
      },
      {
        Id: 'mem_info'/* required */
        MetricStat: {
          Metric: { /* required */
            Dimensions: [
              {
                Name: 'complexcompany'/* required */
                Value: 'zz' /* required */
              },
              /* more items */
            ],
            MetricName: 'memory_used_percent',  
            Namespace: 'plo2'
          },
          Period: 300/* required */
          Stat: 'Average',/* required */
          Unit:'Percent'
        },
        ReturnData: true 
      },
      {
        Id: 'c_drive_free_info'/* required */
        MetricStat: {
          Metric: { /* required */
            Dimensions: [
              {
                Name: 'complexcompany'/* required */
                Value: 'zz' /* required */
              },
              /* more items */
            ],
            MetricName: 'LogicalDisk % Free Space',  
            Namespace: 'plo2'
          },
          Period: 300/* required */
          Stat: 'Average',/* required */
          Unit:'Percent'
        },
        ReturnData: true 
      }
    ],
  };
  cloudwatch.getMetricData(params, function(err, data) {
    if (err) console.log(err, err.stack); // an error occurred
    else{   //console.log(data);           // successful response
 
    var start= JSON.stringify(data.MetricDataResults).indexOf("\"Values\":[");
    var end=JSON.stringify(data.MetricDataResults).indexOf(",\"StatusCode\"");
    var list=JSON.stringify(data.MetricDataResults).substring(start+10,end-1);
    
      var value_obj=JSON.parse(JSON.stringify(data.MetricDataResults));
        
         for(var i in value_obj)
             for(var index in value_obj[i].Values)
               console.log(value_obj[i].Id+" "+index+"  "+parseFloat(value_obj[i].Values[index]).toFixed(2)); //데이터가 소수점6자리까지 출력되서 2자리로 컷
        
    }
  });
  
cs

출력: cpu, memory, disk 값이 288개씩 출력됨(24시간 동안 5분단위 데이터 수집: 288개)

기본적으로 data.MetricDataResults를 stringify한 값을 json으로 파싱하여 출력하면 아래와 같이 나오는데, 위 코드는 문자열로 출력하기위해 values 부분부터 statuscode전까지(=값만 출력) 부분 파싱하여 출력시킨 예제 코드입니다.

문자열이 아닌 json 타입으로, 그리고 최신값, 특정 기간 값 등을 출력하려면 위의 코드를 조금만 수정하셔서 배열로 접근하여 출력시키면됩니다. 

이 문서를 참고하시면 좋습니다.

https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/CloudWatch.html#listMetrics-property

 

Class: AWS.CloudWatch — AWS SDK for JavaScript

The modular AWS SDK for JavaScript (v3), the latest major version of AWS SDK for JavaScript, is now stable and recommended for general use. For more information, see the Migration Guide and API Reference. Class: AWS.CloudWatch Inherits: AWS.Service Object

docs.aws.amazon.com

 

분류를 하자면 이번에 활용한 sdk는 back-end용이고, 서버단에서 처리하기 위한 sdk입니다. 데이터를 읽어들인다고 해도 rest api 설계나 기타 미들웨어 설계를 통해 서버-브라우저 연동 작업이 필요한데, 브라우저에서 곧바로 데이터를 fetch해와서 사용할 수 있는 브라우저용 sdk도 존재합니다. 

1
<script src="https://sdk.amazonaws.com/js/aws-sdk-2.1132.0.min.js"></script>
cs

예를들어 html이나 vue, react 등 다양한 front-end 언어로 사이트 구축 시 브라우저용 sdk를 사용하고 싶다면 html 코드안에 위 코드 한줄을 삽입한 뒤 위에서 살펴본 params코드를 추가하고나서 console.log로 찍어보면 브라우저에서 곧바로 데이터가 출력됩니다. back-end용 sdk를 사용할 경우 직접 설계한 api 호출을 통해 확인 가능한 결과물입니다.

 

Reference

https://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_GetMetricData.html

https://docs.aws.amazon.com/code-samples/latest/catalog/code-catalog-javascriptv3.html

https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/CloudWatch.html#listMetrics-property

https://www.npmjs.com/package/aws-sdk

알람 경보 설정(CPU, Memory, Disk)

하드웨어의 각 리스소 사용량이 설정 기준치를 넘었을 시 대쉬보드를 통한 시각적 경보 효과 및 이메일, Slack을 통한 알람을 구현하는 과정을 정리한다.

  • 경보 환경 세팅
    • window/ubuntu 서버 모두 다양한 방법으로 하드웨어 부하를 테스트 할 수 있고, 그 중 가장 확인하기 쉬운 ubuntu서버에서 cpu부하를 통해 테스트 환경 설정.
      우분투 서버에서(ec2 인스턴스 원격 연결된) sudo yum install stress -y 명령어로 테스트 패키지 설치.
      그 다음 grep -c processor /proc/cpuinfo 명령어로 인스턴스의 cpu 코어 갯수 확인
    • 그 다음 아래 명령어 양식대로 cpu 부하 설정을 위한 코어 갯수 및 부하 기간 설정
      stress --cpu 코어갯수 --timeout 시간(초)
    • top 명령어를 통해 cpu 사용량이 100%까지 올라간 것을 확인.

cpu코어 수
20분동안 cpu util 100%부하
cpu사용량이 100%에 근접

  • 경보 위젯 확인
    • 데이터가 기준치를 넘었을 시 경보 목록과 대쉬보드 페이지에 경보가 생성됨.

 

 Email을 통한 알람 설정

  • SNS topic/subscription 설정: 이메일/Slack 채널 알람을 위해 sns 주제/구독 설정
  • 이메일 알람 확인: cloudwatch 콘솔 -> 경보 탭 '모든 경보' -> 경보 생성
  • 경보 생성 지표를 생성 후 지표 탭에서 경보 확인 기간을 설정(20분)
  • 참고) 기간은 경보를 생성하기 위해 지표를 평가하는 기간으로, 20분으로 설정 시 경보가 20분 당 한 번씩 평가함.(경보가 지속되는 상황에서는 최초 한번의 알람만 울림)
  • 이메일 경보 설정 예시

  • 그리고 경보 -> 조건 탭에서 경보 이벤트를 설정할 데이터 기준치를 설정

5분마다 한번씩 확인

 

cpu사용량이 60%이상일 때 알람설정

  • 추가 구성 탭 에서는 경보를 알릴 데이터 포인트를 설정하는데, default는 1/1로 되어있음.

왼쪽은 데이터 포인트 체크 횟수, 오른쪽은 기간*횟수를 의미함.

 

  • 실제 테스트 결과, 5분에 한 번 꼴로 알람이 울림.
    참고) https://docs.aws.amazon.com/ko_kr/AmazonCloudWatch/latest/monitoring/AlarmThatSendsEmail.html
  • 그리고 그림에서 볼 수 있듯, 데이터 누락에 관한 경보도 설정할 수 있는데,
    다음-> 알림 탭에서 경보상태 트리거를 설정 및 알림 전송 엔드포인트(아까 생성한 SNS토픽 이름)선택-> 경보 이름, 설명 저장 후 경보 생성 완료-> 경보 환경 세팅 후 이메일 확인
  • 예시

Slack 알림 메시지 설정
이메일을 통한 알람 수신은 매우 번거롭고 확인하기 힘들기 때문에 팀에서 사용 중인 slack채널을 통해 빠르게 알람 수신할 수 있도록 설정할 수 있음.

특히 이메일 알람의 경우 기본 format을 편집할 수 없어서 불필요한 정보도 함께 전달되며, 즉각적으로 확인할 수 없음.
Slack알람 순서: 알림 받을 slack 채널 생성-> 수신 webhook 설정-> SNS 주제, 구독 생성-> lambda 함수 설정-> 동작 확인

  • 알람 수신용 채널 생성 및 설정경보 알림 전용 채널을 새로 생성(본인에게 알람 수신 원할 시 별도 생성은 필요하지 않음)

  • 해당 채널로 입장-> 상단 더보기-> 앱 클릭-> 'incoming webhooks' 검색 및 추가

  • 해당 채널의 웹훅 URL(aws에서 이벤트 발생 시 슬랙채널로 알림을 보낼 때 사용하는 주소)이 생성되고, 경보를 수신할 채널 이름, 사용자 지정 후 설정 저장
  • AWS Lambda의 알람 설정 함수가 해당 URL로 알람 전송->slack에서 확인

  • 설정 후 채널에 알림 문구가 도착한 것을 확인

  • 이제 aws sns topic/subscription를 설정해야 하는데, 그 전에 lambda에서 알림 전송용 함수를 생성함.(ARN 주소를 사용하기 위해 람다를 생성해야하므로 함수만 만들어놓고 topic생성하러 가기)

  • 람다 함수의 언어를 선택하고, 본인이 원하는 알람 포맷으로 코딩하여 테스트 진행하고 함수 ARN를 복사 후 새 탭을 열어서 aws sns 페이지로 이동.(python, node js 예제가 많이 있습니다.) 그다음 환경 변수에서 channel이름과 slack채널의 webhook URL을 입력
  • 앞서 진행 했듯 aws sns 주제/구독을 생성하되 구독 생성 시 프로토콜을 lambda로, 엔드포인트를 방금 생성한 lambda 함수 ARN을 입력.
    • 주의) 최초 topic생성의 경우 새 topic를 생성하지만, 앞서 이미 이메일 알람을 위한 topic 생성이 완료된 경우는 subscription만 생성.

  • 설정이 완료된 lambda상태

→ SNS topic인 HW_resource_alarm 트리거가 생성되어있음.

  • aws sns 구독 상태
    • 현재 이메일/slack 알람을 위한 두개의 엔드포인트가 등록되어 있음.

마찬가지로 경보를 생성하고 cpu 부하를 테스트한 뒤 60%가 넘어가면 아래와 같이 슬랙에 알림이 전송됨.

이메일 알람과 slack알람 비교

  • 이메일: 가독성 떨어지고 즉각 수신이 불가, 이메일 format변경 불가
  • 슬랙: 즉각 알람 가능, 알람현황 및 대시보드 공유url을 추가하여 바로 모니터링 할 수 있음.
  • 전체 Flow

내가 사용한 슬랙 알람 Lambda 함수 코드

직접 실습해 보실 수 있도록 제가 사용했던 lambda코드를 첨부합니다.(물론 구글링이 90%..)

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
// 구성 -> 환경변수로 webhook을 받도록 합니다.
const ENV = process.env
if (!ENV.webhook) throw new Error('Missing environment variable: webhook')
 
const webhook = ENV.webhook;
const https = require('https')
 
const statusColorsAndMessage = {
    ALARM: {"color""danger""message":"위험"},
    INSUFFICIENT_DATA: {"color""warning""message":"데이터 부족"},
    OK: {"color""good""message":"정상"}
}
 
const comparisonOperator = {
    "GreaterThanOrEqualToThreshold"">=",
    "GreaterThanThreshold"">",
    "LowerThanOrEqualToThreshold""<=",
    "LessThanThreshold""<",
}
 
exports.handler = async (event=> {
    await exports.processEvent(event);
}
 
exports.processEvent = async (event=> {
    console.log('Event:', JSON.stringify(event))
    const snsMessage = event.Records[0].Sns.Message;
    console.log('SNS Message:', snsMessage);
    const postData = exports.buildSlackMessage(JSON.parse(snsMessage))
    await exports.postSlack(postData, webhook);
}
 
exports.buildSlackMessage = (data) => {
    const newState = statusColorsAndMessage[data.NewStateValue];
    const oldState = statusColorsAndMessage[data.OldStateValue];
    const executeTime = exports.toYyyymmddhhmmss(data.StateChangeTime);
    const description = data.AlarmDescription;
    //const cause = exports.getCause(data);
 
    return {
        attachments: [
            {
                title: `[${data.AlarmName}]`,
                color: newState.color,
                fields: [
                    {
                        title: '시간',
                        value: executeTime
                    },
                    {
                        title: '설명',
                        value: description
                    },
                    // {
                    //     title: '원인',
                    //     value: cause
                    // },
                    {
                        title: '이전 상태',
                        value: oldState.message,
                        shorttrue
                    },
                    {
                        title: '현재 상태',
                        value: `*${newState.message}*`,
                        
                        shorttrue
                    },
                    {
                        //title: '바로가기',
                        title: '알람설정 현황',
                        value: exports.createLink(data)
                    },
                    {
                        title:'대시보드 현황',
                        value: exports.create1Link(data)
                    }
                ]
            }
        ]
    }
}
 
// CloudWatch 알람 바로 가기 링크
exports.createLink = (data) => {
    return `알람 세부 현황 url`;
}
exports.create1Link = (data) => {
    return ` 대시보드 url`;
}
 
exports.exportRegionCode = (arn) => {
    return  arn.replace("arn:aws:cloudwatch:""").split(":")[0];
}
 
 
 
// 이상 지표 중 Band를 벗어나는 경우
exports.buildAnomalyDetectionBand = (data, evaluationPeriods, minutes) => {
    const metrics = data.Trigger.Metrics;
    const metric = metrics.find(metric => metric.Id === 'm1').MetricStat.Metric.MetricName;
    const expression = metrics.find(metric => metric.Id === 'ad1').Expression;
    const width = expression.split(',')[1].replace(')''').trim();
 
    return `${evaluationPeriods * minutes} 분 동안 ${evaluationPeriods} 회 ${metric} 지표가 범위(약 ${width}배)를 벗어났습니다.`;
}
 
// // 이상 지표 중 Threshold 벗어나는 경우 
// exports.buildThresholdMessage = (data, evaluationPeriods, minutes) => {
//     const trigger = data.Trigger;
//     const threshold = trigger.Threshold;
//     const metric = trigger.MetricName;
//     const operator = comparisonOperator[trigger.ComparisonOperator];
//     return `${evaluationPeriods * minutes}분 동안 ${evaluationPeriods}회 이상 ${metric} ${operator} ${threshold}`;
    
// }
 
// 타임존 UTC -> KST
exports.toYyyymmddhhmmss = (timeString) => {
 
    if(!timeString){
        return '';
    }
 
    const kstDate = new Date(new Date(timeString).getTime() + 32400000);
 
    function pad2(n) { return n < 10 ? '0' + n : n }
 
    return kstDate.getFullYear().toString()
        + '-'+ pad2(kstDate.getMonth() + 1)
        + '-'+ pad2(kstDate.getDate())
        + ' '+ pad2(kstDate.getHours())
        + ':'+ pad2(kstDate.getMinutes())
        + ':'+ pad2(kstDate.getSeconds());
}
 
exports.postSlack = async (message, slackUrl) => {
    return await request(exports.options(slackUrl), message);
}
 
exports.options = (slackUrl) => {
    const {host, pathname} = new URL(slackUrl);
    return {
        hostname: host,
        path: pathname,
        method: 'POST',
        headers: {
            'Content-Type''application/json'
        },
    };
}
 
function request(options, data) {
 
    return new Promise((resolve, reject) => {
        const req = https.request(options, (res) => {
            res.setEncoding('utf8');
            let responseBody = '';
 
            res.on('data', (chunk) => {
                responseBody += chunk;
            });
 
            res.on('end', () => {
                resolve(responseBody);
            });
        });
 
        req.on('error', (err) => {
            console.error(err);
            reject(err);
        });
 
        req.write(JSON.stringify(data));
        req.end();
    });
    
}
 
 
 
cs

 

Reference

https://docs.aws.amazon.com/ko_kr/AmazonCloudWatch/latest/monitoring/AlarmThatSendsEmail.html

Cloudwatch 대쉬보드 생성

순서: aws cloudwatch 서비스 접속-> 대쉬보드 탭-> 대쉬보드 생성 클릭-> 위젯 추가->metrics정보 출력 확인

  • 행 위젯 생성
    대시보드 생성 페이지에서 우상단 위젯추가 버튼-> 행 위젯 추가-> 지표 클릭 후 아래 그림처럼 namespace 목록이 출력됨.

  • 본인이 원하는 namespace의 metric 정보를 설정

  • y축의 퍼센트 수치를 0~100처럼 변경하고 싶으면 옵션 탭-> Left Y axis에서 레이블에 percent입력 후 최소/최대 수치 조정-> 단위 표시해제 후 위젯 생성→ percent로 표현되지 않는 metric 정보를 percent로 그래프에 표현하고 싶을 때 반드시 설정함.

  • 참고) disk 정보의 경우 window 서버는 linux서버와 달리 storage의 사용량 정보는 없기 때문에 사용량 정보로 변환하고 싶을 시 "수학 추가" 탭을 통해 아래처럼 식을 조정해야함. METRICS()는 해당 디멘션의 모든 지표들을 나타내고, METRICS(m1)은 m1만을 의미함. 아래 그림의 경우 metric이 하나 이므로 두 표현 모두 같은 의미를 지님. 

  • 경보 위젯 생성
    • 대쉬보드 창에서 본인이 설정한 경보의 정보를 이용하여 다음과 같이 구성할 수 있음. 

  • 번호 위젯 생성
    • 번호 위젯은 이전에 생성한 행 그래프를 수치화된 텍스트로 표현함.

m1: c드라이브의 free_space

m2: d드라이브의 free_space

e1: 잔여 공간이 아닌 사용량을 사용하기 위해 새로 생성한 지표

 

  • 행/번호 위젯 생성 완료

  • 외부 공유설정
    • 생성한 대시보드를 외부에서 참조할 수 있는 기능으로, public url을 통해 누구나 접근할 수 있음.
      cloudwatch 대시보드 탭에서 대시보드 선택-> 대시보드 공유 클릭-> "대시보드를 공개적으로 공유" 클릭-> "정책 수락 및 공유 가능한 링크 생성" 클릭-> 설정 완료 후 대시보드 생성 페이지에서 상단에 주소 링크가 보임. 이 링크를 통해 외부에서 그래프를 모니터링 할 수 있음. 링크 주소를 복사 하여 접속하면 그래프를 볼 수 있지만 읽기 권한만 제공됨.

  • 텍스트 위젯 생성
    • 텍스트 위젯은 aws의 마크다운 언어로 위젯을  자유롭게 구성할 수 있지만 aws에서 지원하는 markdown 문법은 매우 한정됨.(사용 지양 추천)

  • 서버 대시보드 구현상태

  • 다양한 위젯을 구성한 대시보드

 

Reference

https://docs.aws.amazon.com/ko_kr/awsconsolehelpdocs/latest/gsg/aws-markdown.html

https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/create-and-work-with-widgets.html

AWS Cloudwatch

AWS의 리소스 상태를 실시간으로 모니터링 할 수 있는 서비스로, 데이터 수집, 알람 생성, 모니터링도구(로그, 대쉬보드)를 제공한다.(구체적인 설명은 아래 링크에 잘 나와있습니다.)

https://docs.aws.amazon.com/ko_kr/AmazonCloudWatch/latest/monitoring/WhatIsCloudWatch.html

 

AWS Cloudwatch 구조

애플리케이션 환경에서 agent를 설치 후 데이터를 전송하면, aws에서 metric이라는 이름으로 수집되는데, 해당 메트릭은 Namespace 내부의 Dimension(차원) 안에 저장되어있다. 데이터를 가공하기 위한 다양한 math expression이 제공되며 가공처리 후 최종적으로 대시보드에 뿌려주게 되는 구조로 이루어져있다.

1. Window server -On premise 방식

          aws cli 설치 이후 powershell이나 cmd에서 "aws --version" 명령어가 인식되어야 함.

  • Cloudwatch agent 프로필 등록(region, data format등의 AmazonCloudWatchAgent 프로필을 생성하기 위함)
    • "aws configure --profile AmazonCloudWatchAgent" 명령어 입력 후 region은 ap-northeast-2, format은 json으로 설정
  • IAM권한 설정(agent 구성 파일 생성)
    • 아래 명령을 입력하여 구성 파일 생성
      • cd "C:\Program Files\Amazon\AmazonCloudWatchAgent".\amazon-cloudwatch-agent-config-wizard.exe
      • 명령어를 입력 하면 구성 파일 설정을 위한 옵션들을 선택하게 됨.(OS, 지표 타입, 전송 주기 등등)
      • 우선 대부분의 진행 옵션을 default로 선택하되 Log Agent/ log files/log/ SSM Parameter store와 관련된 옵션은 모두 No로 설정.
      • wizard실행 방식은 json 파일의 기본 format을 사용하기 위해 사용되고, 본인이 직접 config.json 파일을 생성하며 편집해도 됨.
  • 구성 파일 편집
    • cloudwatch에 연동을 시키면 default namespace가 생성되는데, 각 서버에 따라 이름을 따로 지어주기 위해 이전 단계를 통해 생성된 json 파일을 편집함(일단 이름만 변경)
    • 현재 디렉토리 위치에 있는 config.json 파일을 open한 뒤 namespace설정 구문 한 줄을 입력(winserver1은 본인이 설정한 namespace임.)

  • cloud watch agent 실행
    • 실행: & "C:\Program Files\Amazon\AmazonCloudWatchAgent\amazon-cloudwatch-agent-ctl.ps1" -a fetch-config -m onPremise -s -c file:"C:\Program Files\Amazon\AmazonCloudWatchAgent\config.json"         - fetch-config: config.json
    • 상태 조회(stopped/running): fetch-config를 status로 변경
    • 중지: fetch-config를 stop으로 변경

  • agent 실행 로그 파악
    • ubuntu는 /opt/aws/amazon-cloudwatch-agent/logs 위치에 로그 파일이 존재. 이 파일을 조회하여 agent의 동작 상태 확인(ex. config.json 파일 문법 오류, 기타 에러 등)
    • windows는 C:\ProgramData\Amazon\AmazonCloudWatchAgent\Logs\amazon-cloudwatch-gent.txt
  • 지표 생성 확인
    • 위에서 살펴본 실행 명령의 결과로 aws cloudwatch 콘솔에 본인이 생성한 namespace의 메트릭스 정보가 뜸.
    • winserver1이 생성됨(cpu, disk, network 등의 metrics 정보 존재)
    • 나머지 3개의 namespace는 본인이 기존에 만들어 놓은 것.

 

2. Window Server -AWS EC2 방식

EC2방식은 Window/Linux 상관없이 Cloudwatchagentserverpolicy 역할을 생성 한 뒤 EC2인스턴스 페이지에서 해당 역할을 추가하면 됨. on premise방식과 유사하지만 IAM 권한 설정 및 에이전트 실행 명령어 옵션이 상이함.

3. Linux -AWS EC2 방식

  • cloudwatch agent 패키지 설치
    • sudo yum install amazon-cloudwatch-agent
  • IAM 권한 설정
    • window EC2방식과 동일
  • agent 구성 파일 생성
    • sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-config-wizard
  • 구성 파일 편집
    • 마찬가지로 namespace를 새로 지정하려면 cd /opt/aws/amazon-cloudwatch-agent/ 디렉토리로 이동한 뒤 config.json을 open한 뒤 이전과 마찬가지로 namespace 구문 한 줄 추가 후 저장.
  • cloudwatch agent실행
    • sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -s -c file://opt/aws/amazon-cloudwatch-agent/bin/config.json
    • 주의) 공식 문서에는 아래와 같이 나와있지만, 실제로 "file:" 뒤에 슬래쉬( / ) 2개 추가 안해주면 cloudwatch 콘솔에서 namespace가 정상적으로 생성되지 않는 이슈 존재

 

Reference

https://docs.aws.amazon.com/ko_kr/AmazonCloudWatch/latest/monitoring/install-CloudWatch-Agent-on-EC2-Instance.html

 

간단하게 윈도우 cmd창에서 ssh를 이용하여 ec2인스턴스에 접속해보는 방법을 알아보겠습니다.

AWS EC2를 사용할 때 원격 접속 툴로 putty, TeraTerm 등 다양한 방법으로 접근할 수 있는데 

특히 저는 TeraTerm을 이용하고 있습니다.

 

근데 사실 로컬에서 어떤 SW설치없이 가볍게 EC2에 접근할 수도 있습니다.

굳이 권장되는 방법은 아니지만 대충 cmd창에서도 서버에 원격 접속할 수 있구나 , 정도로 이해하면 좋을 것 같습니다.

 

우선 로컬에서 ssh를 이용하여 서버에 접근하기 위해 "openssh 클라이언트"가 깔려있는지 확인해야합니다.

 

아래 순으로 확인해보시길 바랍니다.

1. 화면 왼쪽 하단 돋보기에 "설정" 이라고 검색 -> 실행

2. 앱 클릭-> 오른쪽 부분에 "선택적 기능" 클릭

3.  ssh 검색하여 OpenSSH 클라이언트가 깔려있는지 확인( 없다면 install 하기)

 

 

이제 cmd창에 접속합니다.

이 때 주의할 점은 EC2에 접속하기 위해 사용되는 .pem키가 있는 디렉토리에서 원격접속을 시도해야합니다.

만약 바탕화면에 키를 저장해놓고 C:\Users\admin에서 접속하려고하면 접속이 안됩니다.

저는 admin 디렉토리에 키를 저장해 놨습니다. 이제 접속해 볼텐데 아래 순으로 명령어를 입력하면 됩니다.

 

* 맥과 윈도우는 각각 명령어 형태가 다릅니다. 저는 windows기준으로 실행했습니다.

하나씩 설명해보겠습니다.

- C:\Users\admin: 키가 저장되어있는 디렉토리의 절대경로

- 흰색 부분.pem: 키 파일 이름

- ubuntu: EC2인스턴스 사용자 이름

- @ec2-100-25-139-116.compute-1.amazonaws.com: 인스턴스의 퍼블릭IPv4 DNS주소 이름

 

이렇게 작성 후 엔터를 누르면 아래와 같이 인스턴스에 접속됨을 알 수 있습니다.

에러가 났다면 <키 파일 위치, 오타, 인스턴스 ip> 이 중에서 문제가 있는 경우이므로 다시 확인 후 실행해봅니다. 

정상적으로 접속이 된 것을 확인할 수 있습니다.

 

 

이번시간에는 간단하게 우리가 구축했던 EC2 우분투 인스턴스를 로컬환경에서 접근하여 

우분투<-> 로컬 간 파일 전송을 해보도록 하겠습니다. 

 

파일질라(FileZilla)라는 것을 사용하는데, 이 툴을 이용하여 서버에 있는 파일들을 로컬에 이동시키거나

반대로 로컬에 있는 파일을 서버로 쉽게 옮길 수 있습니다.

 

로컬-서버 간 파일 전송을 위한 방법에는 다양한 방법들이 있는데 저는 주로 삼바를 이용했습니다.

리눅스/라즈비안에 삼바서버를 설치하여 로컬<->해당OS간 파일전송을 할 수 있었습니다.

 

오늘 다뤄볼 파일 질라는 매우 직관적이고 windows환경에 친숙한 GUI툴이기 때문에 사용이 쉬운 장점이 있습니다.

 

우선 파일질라 설치를 위해 아래 링크로 접속합니다.

https://filezilla-project.org/

 

FileZilla - The free FTP solution

Overview Welcome to the homepage of FileZilla®, the free FTP solution. The FileZilla Client not only supports FTP, but also FTP over TLS (FTPS) and SFTP. It is open source software distributed free of charge under the terms of the GNU General Public Licen

filezilla-project.org

그 다음 아래 화면에서 "Download FileZilla Client"를 클릭합니다.

 

이제 아래 버튼을 클릭하여 설치를 시작합니다.

 

설치완료 후 접속하면 아래와 같은화면이 뜹니다. 빨간 색 박스 부분을 클릭하여 우리가 접근할 서버를 세팅할 수 있습니다.

왼쪽 화면은 본인의 로컬 디렉토리를 나타내고 있습니다.

새로 뜬 화면에서 우선 아래 My Sites디렉토리에 New site버튼을 클릭한 뒤 간단하게 이름을 지정해줍니다.

저는 "과제용" 이라고 이름 지어주었고 모든 설정을 완료하면 아래와 같이 파일질라 이름이 표현됩니다.

 

사이트를 추가해 준 뒤 오른쪽 화면에서 몇가지 설정을 해주어야합니다.

1. Protocol-> SFTP로 설정(ssh로 보면됨, EC2에 접근할 때 ssh를 사용하기 때문)

2. Host:-> EC2 인스턴스의 퍼블릭 IPv4 DNS주소를 입력, Port: 22 입력

2번의 정보는 원래 IP를 입력해도되지만 학교-집에서 EC2를 쓰다보면 Ip가 변경되는 경우가 많아서 불편한데,

대신 IPv4 DNS주소는 변동이 없기 때문에 이 정보를 주로 사용합니다.

3. Logon Type: Keyfile로 설정(우리가 Ec2에 접근할 때 .pem 키파일로 접근한 것과 동일)

4. User: 유저이름으로 설정( 본인은 Ubuntu로 되어있음)

5. Key File: 해당 인스턴스의 .pem키파일 등록

이렇게 설정해준 뒤 Connect버튼을 누르면 정상 연결이 될 것입니다.

정상적으로 연결이 되었다면 아래처럼 왼쪽에는 로컬디렉토리가, 오른쪽엔 우분투의 디렉토리가 뜨게됩니다.

 

파일 전송은 매우 간단한데, 로컬->서버로의 파일 전송은 upload이고 서버->로컬로의 파일전송은 Download입니다.

간단하게 우분투 서버에 있는 file2라는 파일을 로컬로 이동시켜보겠습니다.

 

파일의 마우스 우클릭을 통해 Download를 클릭하면 아래 창 처럼 로컬admin디렉토리에 file2가 저장되었음을 알 수 있습니다.

반대로 로컬의 파일을 서버로 upload해보겠습니다. 클라우드.txt파일을 전송 시, 아래처럼 서버에 잘 저장되었음을 알 수 있습니다.

 

이것으로 파일질라를 이용해 간단하게 로컬과 서버 간에 파일 전송을 해보는 작업을 해보았습니다.

 

 

이번시간에는 저번에 생성한 DB를 이용하여 테이블작성, 데이터 입력 쿼리문 등을 작성해보겠습니다.

저번시간에 생성한 DB는 Root계정 권한으로, 모든 테이블에 접근 가능한 권한을 가졌습니다.

이번에는 Root가 아닌 새로운 계정을 생성하여 DB작업을 실행해보겠습니다.

 

데이터베이스 생성-> 유저 생성-> 유저에 권한 부여-> 생성한 유저를 이용하여 작업 DB에 접속하는 순으로 이루어집니다.

 

첫 번 째로 DB를 생성해보겠습니다.

create databases DB이름; 으로 쿼리문을 작성하고 show databases;쿼리로 결과를 확인해보겠습니다.

새로운 jow1025db가 생성되었습니다. 이제 새 유저를 생성하고 유저에게 해당 DB에 모든 접근권한을 부여하겠습니다.

 

create user 'test'@'%' identified by 'cloud';

유저이름: test

유저 비밀번호: cloud

권한을 부여할 DB이름: jow1025db(위에서 만든)

 

아래는 권한을 부여하는 쿼리문입니다.
grant all privileges on jow1025db.* to 'test'@'%';

권한을 즉시 적용하는 쿼리문입니다.

flush privileges;

3가지 쿼리문을 모두 수행했을 때 워크벤치 창 아래 input 화면에 오류가 없어야합니다.

 

이제 워크벤치 홈에서 새로 생성한 user를 이용해 새로 생성한 작업 database에 커넥트 해보겠습니다.

 

아래와 같이 작성 후 Test connection으로 확인(여기서 유저 비밀번호 'cloud'입력)후 OK를 클릭합니다.

유저네임은 생성한 유저이름을, 호스트네임에는 엔드포인트 주소를 입력하면됩니다.

에러가 났다면 DB생성->유저생성 단계에서 에러가 난 것이므로 다시 진행해야합니다.

 

연결 후 새로 생긴 작업창에서 show databases; 쿼리로 아까 생성했던 DB명이 잘 나오는지 확인합니다.

 

여기까지 진행 했을 때 오류가 났다면 생성단계부터 다시 해보시길 바랍니다.

 

이제 테이블을 생성해서 몇가지 데이터를 생성해보겠습니다.

use jow1025db;(DB명) 쿼리문으로 작업 db에 연결한 뒤 3~14행을 차례대로 작성합니다.

14행 결과가 다음 처럼 나와야합니다.

 

이제 DB작업은 모두 끝났습니다. 이제 마지막으로 php와 연동하여 간단한 웹 페이지에서 DB에 데이터를 저장했을 때 

입력한 데이터가 워크벤치에서 테이블에 반영이 잘 되었는지 확인할 차례입니다.

 

EC2로 돌아와서 Php와 Mysql 연동 라이브러리를 설치해보겠습니다. 아래 명령어를 실행합니다.(디렉토리 위치는 상관없음)

sudo apt install php5.6-mysql

 

실행이 완료되었고 이제 Mysql과 Apache를 재시작합니다.

sudo service mysql restart
sudo apachectl restart

관련 패키지가 모두 정상적으로 설치되었는지의 여부를 확인합니다. 모두 잘 깔려있습니다.

dpkg -l | grep php

이제 /var/www/html/ 디렉토리로 이동하여 php스크립트를 생성해보겠습니다.

우선 create.php 파일을 만듭니다. 이 파일은 간단하게 웹 페이지에서 책 제목/본문/저자를 입력하는 화면의 코드입니다.

해당 파일에 아래와 같은 소스코드를 복붙하여 넣어줍니다. 이때 주석 친 파란색 부분 줄은 

각자 본인의 엔드포인트주소, 유저이름, 비밀번호, DB이름으로 바꿔주어야합니다. 

 

<소스코드>

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
<?php
$conn = mysqli_connect(
  'lecture-db21.cigtm9yqh76m.us-east-1.rds.amazonaws.com', // 본인 주소
  'test',
  'cloud',
  'clouddb'); // 데이터베이스 이름
 
$sql = "SELECT * FROM topic";
$result = mysqli_query($conn, $sql);
 
?>
<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <title>WEB</title>
  </head>
  <body>
    <h1>KPU University</h1>
    <h3> 원하는 책의 제목 본문 저자를 입력하세요. </h3>
    <form action="process_create.php" method="POST">
      <p><input type="text" name="title" placeholder="제목 (ex)Harry Porter"></p>
      <p><input type="text" name="description" placeholder="본 문  (ex)magic stone"></p>
      <p><input type="text" name="author" placeholder="저자  (ex)rolling"></p>
      <p><input type="submit"></p>
    </form>
  </body>
</html>
cs

 

이제 process_creaete.php파일을 생성합니다. 이 파일은 데이터의 저장 성공 여부를 확인하는 화면의 코드입니다.

마찬가지로 똑같은 4줄을 각자 본인 설정에 맞게 바꿔준 뒤 저장하고 빠져나옵니다.

<소스코드>

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
32
<?php
$conn = mysqli_connect(
  'lecture-db21.cigtm9yqh76m.us-east-1.rds.amazonaws.com',
  'test',
  'cloud',
  'clouddb');
  # title, description 이라는 사용자가 입력한 정보를 그대로 php에 입력하는 행위는 보안에 취약, 따라서 관리 필요
 
  $filtered = array(
    'title'=>mysqli_real_escape_string($conn$_POST['title']),
    'description'=>mysqli_real_escape_string($conn$_POST['description']),
    'author'=>mysqli_real_escape_string($conn$_POST['author'])
  );
 
$sql = "
  INSERT INTO topic
    (title, description, author, created)
    VALUES(
      '{$filtered['title']}',
      '{$filtered['description']}',
      '{$filtered['author']}',
        NOW()
    )
";
$result = mysqli_query($conn$sql);
if($result === false){
  echo '저장하는 과정에서 문제가 생겼습니다. 관리자에게 문의해주세요';
  error_log(mysqli_error($conn));
else {
  echo '성공했습니다. <a href="create.php">돌아가기</a>';
}
?>
cs

 

모두 저장을 완료했으면 이제 웹 페이지에서 동작을 확인해볼 차례입니다.

크롬에 EC2 퍼블릭 ip주소/create.php를 입력하면 아래와 같은 화면이 뜨게됩니다.

간단하게 제목, 본문, 저자를 각각 jow1025/jow/1025라고 저장하고 "제출"버튼을 클릭하면 아래와 같은 화면이 뜹니다.

돌아가기버튼을 클릭하면 다시 입력할 수 있구요.

 

입력했던 내용이 DB에 잘 반영이 되어있다면 실습은 모두 끝입니다. 워크벤치에서 확인해보겠습니다.

select* from topic; 을 입력해봅니다.

위처럼 아까 입력했던 jow1025/jow/1025가 잘 저장되었음을 확인할 수 있습니다!

 

이로써 EC2를 이용하여 웹서버를 구축하고 데이터 베이스를 처리하기 위한 서버를 따로 구축하여(RDS)

간단한 웹사이트를 구축하여 이들을 연동하는 작업을 해보았습니다.

 

신기하지않나요?? 

 

EC2와 RDS 모두 클라우드 형태로 구현하여 접근권한만 설정해주고 로컬에서 별도의 SW 설치없이 모두 가상의 자원을 이용하여 작업했습니다.

우리가 물리적으로 소모한 자원없이 모두 클라우드 서버 형태로 구현되어있기 때문에 매우매우 간편한 것 같습니다.

왜 돈을 내고 클라우드 서비스를 이용하는지 이제야 조금 알것 같습니다..(아마존, 구글, MS 등...)

 

최종적으로 우리가 구현한 시스템은 아래와 같습니다.

 

 

 

 

이번시간에는 DB서비스 구축을 위해 알아보겠습니다.

지금까지 저희는 DB사용을 위해 AWS EC2에서 mysql을 설치했고 워크벤치를 이용해 DB연동을 확인했습니다.

설치 과정에서 외부사용자가 EC2 인스턴스 실행중일 때 DB를 자유롭게 접근할 수 있도록 설정을 해주었습니다.

결과적으로 저희의 시스템은 EC2내부에서 웹서버를 구축했고 DB서비스까지 구축했습니다. 

하지만 DB서비스와 웹서버를 동시에 이용하므로 성능 상 효율이 떨어질 수 있기 때문에 DB서버를 따로 구축하여 DB만 접근할 수 있도록 한다면 훨씬 편할 것 입니다.

우리는 외부의 DB접근까지는 설정해주었기 때문에 이 DB를 접근할 데이터베이스 관리시스템이 필요합니다.

 

우리는 AWS가 제공하는 관계형 데이터베이스 관리시스템(RDBMS)인 RDS(Relational Database Service)를 이용하여 외부의 DB서버를 구축하여

데이터베이스를 관리할 수 있도록 해보겠습니다.

 

가장 먼저 AWS 콘솔에서 RDS 인스턴스를 생성해야합니다. AWS educate 콘솔창에서 상단 서비스탭에서 데이터베이스영역의 RDS를 클릭합니다.

그 다음, 화면 상단의 데이터베이스 생성 버튼을 클릭합니다.

 

데이터베이스 생성창에서 모든 설정은 디폴트로 놔두고 아래 사항만 바꿔줍니다.

1. 엔진유형: MySQL

2. 템플릿: 프리티어(공짜로 이용해야하기때문에.... 단 1년이 지나면 과금이 될 수 있습니다.)

3. DB인스턴스 식별자: 마음대로 바꿔주기(저는 jow1025cloud로 바꿨습니다.)

4. 자격 증명 설정에서 로그인 ID와 마스터 암호 생성(저는 ID를 root로 생성했습니다.)

5. 퍼블릭 엑세스 기능: "예" ( 외부에서 원격으로 DB를 접근하기위함)

 

이렇게 수정하여 DB를 생성하고 왼쪽 탭의 '데이터베이스'를 클릭하면 약간의 시간이 소요된 후 인스턴스가 생성됨을 알 수 있습니다.

생성된 인스턴스를 클릭하면 아래와 같이 엔드포인트 도메인 주소가 뜨는데, 이것을 복사해 둡니다.

EC2에서는 퍼블릭 ip주소로 EC2를 접속하고, RDS는 이 엔드포인트 주소를 통해 접속합니다.

 

이제 추가로 보안설정을 해줘야합니다. 

우리는 EC2 인스턴스의 보안그룹에서 ssh, http연결을 허용했기 때문에 ssh로 EC2 터미널을 접근할 수 있었고 웹을통해

apache, php 연결을 확인할 수 있었습니다.

 

이렇듯, 저희는 RDS로 DB서버를 따로 구축하여 EC2나 다른 외부 컴퓨팅 환경에서 이 서버의 Mysql DB를 접근하게되는데, 이 때 외부에서 RDS의

Mysql로 데이터를 전송/요청하고 RDS에서 데이터를 반환하는 등의 프로토콜 통신을 위해 보안그룹에서 따로 설정을 해줘야합니다.

   

연결&보안 탭에서 보안그룹을 클릭합니다.

 

보안그룹 창이 뜨면 보안설정을 해줄 보안그룹ID를 클릭합니다.

 

오른쪽 하단의 '인바운드 규칙 편집' 을 클릭합니다.

인바운드 규칙 창에서 아래 '규칙 추가'버튼을 클릭한 뒤 아래와 같이 설정한 뒤 '규칙 저장' 버튼을 누르고 빠져나옵니다.

Mysql을 사용하므로 Mysql/Aurora로 맞추고 어디서든 접근할 수 있도록 소스를 위치무관으로 바꿔주었습니다.

아래와 같이 저장이 되어있을 것입니다.

이로써 설정이 완료되었습니다. 이제 EC2에서 SQL쿼리로 RDS의 Mysql DB를 접근할 때 주소(엔드포인트)를 통해 접근할 수 있습니다.

실제로 접근해보겠습니다. 테라텀으로 EC2에 접속한 뒤 아래 명령어로 RDS mysql에 접근합니다. 

mysql -u root -p -h 아까 복사한 엔드포인트 주소

 

비밀번호는 아까 RDS인스턴스에서 DB를 생성할 때 설정한 비밀번호를 입력하면됩니다.

터미널에서 DB를 접근해서 사용하는 것은 불편하므로 워크벤치를 이용해 똑같이 접근해보겠습니다.

아래 '+'버튼을 클릭합니다.

아래와 같이 설정해준 뒤 test connection을 눌렀을 때 문제가 없을경우 ok버튼을 클릭합니다.

Connection Name은 편하게 지어주고 Username과 password는 인스턴스에서 DB생성 때 설정한 정보를 입력합니다.

독특한 점은 hostname에 ip주소가 아닌 엔드포인트 주소를 설정한다는 점입니다. 

위에서 언급했듯이 우리는 RDS의 DB에 접근할 때 ip가아닌 RDS 인스턴스의 엔드포인트 주소로 접근할 수 있습니다.

 

문제없이 완료되었다면 성공이고, 에러가 났다면 처음부터 다시 진행해보는게 맘 편합니다...

 

간단하게 show database; 쿼리문으로 결과를 확인하는 것으로 이번 실습 마치겠습니다.

 

우리가 구축한 시스템의 간단한 구조도는 다음과 같습니다.

다음번에는 실제 insert, select 등의 쿼리문으로 작업을 진행해보겠습니다.

 

 

+ Recent posts