티스토리 뷰

3. AWS로 워드프레스 구축하기(3) 워드프레스 배포

웹서버에 워드프레스 서비스를 기동하기 위한 환경을 마련했습니다. 그럼 외부에서 웹서버로 접근하기 위한 요소들을 설정할 겁니다.

앞에 글에서 언급한 바와 같이, 로드밸랜서를 통해 HTTP request를 받아 web server를 전달할 겁니다.

그 해당 로드밸런서를 어떻게 구성하는지를 확인해볼 예정입니다.

로드밸런서의 경우, 오토 스케일링이라는 서비스를 통해 같이 만들겁니다.

오토스케일링 설정하면, 로드밸런스를 같이 생성할 수 있습니다.

  1. Auto Scailing : 시스템 자원들을 모니터링하여 필요에 따라 서비스를 확, 축소하는 서비스. 웹서비스 필요에 따라서 웹서버를 필요만큼 확장할 수 있습니다. 이를 통해 운영의 탄력성을 확보할 수 있습니다. 우선 오토스케일링을 하기 위해서는 인스턴스의 템플릿이 필요합니다. 해당 서비스는 인스턴스 template을 통해 인스턴스를 유연하게 늘리개 때문에 템플릿을 설정해야 합니다.
  • 이를 위해 기존 웹서버에서 1) 이미지 생성 2) 탬플릿 생성을 진행해야 합니다.

1) 이미지 생성, 템플릿 생성

  • 이미지 생성 : 이름과 설명을 설정해준다 .

  • 탬플렛 생성

  • EC2-> Autoscaling -> 생성

  • 금방 만들었던 템플릿을 설정해준다.
  • 이후에 프라이빗 서브넷에 생성되도록 설정해준다.

  • 추가적인 설정으로 마무리 해준다.

  • 여기서 새로운 로드밸런서를 생성할 수 있다.
  • 그리고 웹서버(HTTP)를 로드밸런싱하는 것이기 때문에 Application Load Balancer(HTTP)로 설정한다.
  • 그리고 Internet-Facing으로 설정해서 인터넷으로 request를 받을 수 있게 해야한다.
  • 또한 외부에서 접속으로 해야하기 떄문에 퍼블릭 서브넷으로 설정해야한다.
  • 그리고 평소에는 2개 웹서버를 유지하고 최소 한개, 최대 4개가 되도록 설정한다.

  • 평소에는 서버가 2개로 유지되는 것이고, 필요에 따라서 최대 4개, 최소 1개로 줄어든다.
  • 이렇게 오토스케일링 그룹이 설정된다. 그리고 이후에 정상적으로 로드밸런서로 들어갈 수 있는지 확인해야 한다.

  • 이 DNS로 브라우저에 입력하면 워드프레스 페이지가 뜬다. 그럼 완성!

4. AWS로 워드프레스 구축하기(4) 도메인 설정

자 이제 워드프레스 배포도 되었습니다. 근데 매번 아마존 로드밸런서 DNS로 접속할 수는 없잖아요? 그래서 이젠 도메인 하나 구매해서, 진짜 웹사이트 이용하는 거 처럼 설정할 겁니다.

  1. 도메인 구매
  • 저는 여기서 도메인 구매를 하였습니다. 본인이 원하시는 도메인을 검색하셔서 구매를 진행합니다! (https://www.gabia.com/)

2. 구매한 도메인을 AWS서버와 연결

  • Route53 서비스로 들어갑니다.
    • Route53 : AWS에서 제공하는 도메인이름 웹서비스 (DNS), URL로 접속시 연결된 도메인, 웹서버로 연결되도록 돕는 서비스.
  • Route53-> 호스팅영역 -> 호스팅영역생성을 누룹니다.

  • 만들어진 이후, 해당 호스팅 영역을 들어가서 레코드 생성을 누릅니다.

  • 이후 아래와 같이 설정합니다. 레코드 이름의 경우, 본인이 원하는 레코드로 설정해도 됩니다!

  • 그리고 나서 해당 레코드가 호스트영역에 생성되는 것을 확인할 수 있습니다.
  • 그럼 다시 가비아로 가서, My 도메인 -> 도메인 관리 들어갑니다. 네임서버 설정에 들어가서, AWS에 있는 해당 4개의 주소를 가비아 네임서버에 붙여넣습니다.

  • 그리고 자신이 등록한 레코드(저의 경우 blog.jwitstudy.life)로 접속하면 본인이 배포한 워드프레스가 보일겁니다! 그럼 완성!

3. HTTPS 설정

  • 근데 아래와 같이 안전하지 못한 연결이라고 뜨면서 주의 요함 경고가 뜰겁니다.

  • 이는 HTTP로 하는 해당 접속이 암호화되지 않은 연결이라 보안에 취약하다는 것 입니다.
  • 이를 보완하기 HTTPS를 통해 암호화된 접속을 실행하고 있습니다.
  • HTTPS접속을 위해서는 해당 사이트의 신뢰성을 보증하는 인증서가 필요한데, 인증서 관련 AWS ACM(AWS Certificate Manager)를 통해서 진행할 수 있습니다.
  • ACM에 들어가서 인증서 요청을 합니다.
  • 퍼블릭인증서 요청 유형으로 선택한 다음, 아래와 같이 설정합니다.

  • 앞에 * 와일드카드를 사용하여, 자신의 도메인을 가지는 모든 도메인에 대한 인증도 할 수 있습니다. 그 외 설정은 그대로 진행합니다.

  • 그 이후에 해당 인증서를 클릭한 뒤 ‘Route S3에 레코드 생성’에 들어갑니다.

  • 그러면 바로 본인이 생성했던 레코드가 뜨고, 해당 레코드를 선택한 다음, 레코드 생성을 누르시면 됩니다.
  • 그리고 기다리시면 발급상태에 ‘발급됨’이 뜨면 완성입니다!
  • 그런 다음 해당 로드밸런서가 HTTPS를 요청을 처리할 수 있도록 설정해야합니다.
  • EC2->로드밸런서-> 리스너 및 규칙 -> 리스너 추가를 합니다.

  • 위와 같이, 리스너를 설정하여 추가합니다.
  • 인증서의 경우 본인이 만들었던 인증서를 불러오고, HTTPS 443포트로 설정합니다.
  • 이렇게 해서 다시 본인 블로그 주소로 접속하면,

  • https가 이제 되는 것을 확인할 수 있습니다.
  • 하지만 문제가 있습니다. ‘본인 블로그 주소/wp-admin’ 으로 해서 관리자 페이지에 들어가야 하는데, https의 경우 접속이 안되는 경우가 발생합니다. 그리고 본인이 만든 글을 들어가고자 하면, 주소가 등록한 주소가 아닌 임의의 주소로 변하는 등의 오류 현상이 발생합니다.
  • 현재 워드프레스에서 설정된 홈 주소가 제대로 설정되지 않아서 그런겁니다.
  • putty로 해서 본인 웹서버로 다시 접속합니다. 웹서버 /var/www/html 에 있는 wp-config.php 워드프레스 설정 파일을 바꿔야 하는데.
  • 23번째 줄 경에 해당 config를 본인 블로그 주소로 해서 추가하시면 됩니다.
define(‘WP_HOME’,’http://blog.jwitstudy.life’);
define(‘WP_SITEURL’,’http://blog.jwitstudy.life’);
  • 그리고 마지막 줄 // require_once가 포함되어있는 주석 전에 97번째 줄 전에
//Begin Really Simple SSL Server variable fix
$_SERVER[“HTTPS”] = “on”;
//END Really Simple SSL
  • 위에 해당 설정을 추가해주면 끝입니다!
  • 그러면 다음에는 멀쩡하게 https 접속이 되는걸 확인할 수 있습니다!

이상으로 워드프레스 배포는 끝입니다! 부족한 부분이나 개념적인 부분은 따로 올리거나 글에서 추가할 예정입니다!

 

5. AWS로 워드프레스 구축하기(5) 모니터링 설정

보통 큰 시스템 운영하면 EMS 등을 통해서 서버 이상 시, 담당자에게 문자가 보내집니다. AWS에 서버 이상시 메신저로 보낼 수 있도록 모니터링 기능을 설정하겠습니다.

사용한 메신저는 Slack입니다.

  1. 슬랙 회원가입

Slack은 생산성 플랫폼입니다 | Slack

해당 링크를 통해 Slack 회원가입을 진행합니다.

자 그럼 위와 같이 워크스페이스 생성하라고 나오는데, 워크스페이스 생성해줍니다.

  • 그리고 본인 워크스페이스 해당하는 곳에 클릭하고, 도구 및 설정 -> 앱관리로 들어갑니다.

  • 검색창에 web-hook 를 검색합니다.
  • 그리고 수신 웹후크를 추가해줍니다!

  • 채널은 본인이 아까 만든 채널에 연결합니다.

  • 자 그럼 웹후크 각종 설정이 나올겁니다.
  • 여기서 웹후크 URL은 따로 보관합니다. (나중에 쓸 일이 있습니다)

2. AWS SNS 설정

  • SNS : 구독형 알림 서비스 , 구독된 이벤트 발생시, 메세지를 전달하는 서비스
  • AWS Cloudwatch에서 서버 자원을 모니터링합니다.
  • 이상이 발생하면 SNS에서 경보가 전송되고 SNS는 람다를 호출합니다.
  • 람다에 정의된 Slack에 메세지를 보내는 함수가 실행됩니다.
  • SNS서비스 -> 주제 -> 주제 생성

  • 위와 같이 유형을 표준으로 두고, 원하는 이름으로 설정합니다.

3. CloudWatch

  • 이젠 CloudWatch에서 경보를 생성하겠습니다
  • CloudWatch -> 경보생성
  • 지표선택 누르시고 EC2 -> 유형은 CPU-utilization 이고 본인 웹서버 ec2로 해서 찾습니다.

  1. 그렇게 선택하고 나서, 밑에 임계 값을 원하는 설정으로 정합니다.

  • 그리고 나서 아까 만든 SNS 주제와 연결합니다.

  • 그런 다음 그대로 계속 다음 진행해서 생성합니다.

4. Lambda

  • Lambda는 서버리스 서비스로, 서버 설치 없이 서비스를 제공할 수 있는 서비스 입니다.
  • Lambda -> 함수 생성

  • 이름하고, 런타임 노드 버전만 바꿉니다. (최신 Node.js로 하면 안됩니다.)

자 그럼 밑에 코드가 있는데, 아래 코드를 추가합니다. 기존 꺼 밑에다가 붙여넣습니다.

// 구성 -> 환경변수로 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) => {
    const snsMessage = event.Records[0].Sns.Message;
    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,
                        short: true
                    },
                    {
                        title: '현재 상태',
                        value: `*${newState.message}*`,
                        short: true
                    },
                    {
                        title: '바로가기',
                        value: exports.createLink(data)
                    }
                ]
            }
        ]
    }
}

// CloudWatch 알람 바로 가기 링크
exports.createLink = (data) => {
    return `https://console.aws.amazon.com/cloudwatch/home?region=${exports.exportRegionCode(data.AlarmArn)}#alarm:alarmFilter=ANY;name=${encodeURIComponent(data.AlarmName)}`;
}

exports.exportRegionCode = (arn) => {
    return  arn.replace("arn:aws:cloudwatch:", "").split(":")[0];
}

exports.getCause = (data) => {
    const trigger = data.Trigger;
    const evaluationPeriods = trigger.EvaluationPeriods;
    const minutes = Math.floor(trigger.Period / 60);

    if(data.Trigger.Metrics) {
        return exports.buildAnomalyDetectionBand(data, evaluationPeriods, minutes);
    }

    return exports.buildThresholdMessage(data, evaluationPeriods, minutes);
}

// 이상 지표 중 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) => {
            reject(err);
        });

        req.write(JSON.stringify(data));
        req.end();
    });
}
  • 이후에 Deploy를 눌러 배포합니다.
  • 그리고 구성으로 들어가서 환경변수 탭에 들어가고, 아래와 같이 환경변수를 추가해줍니다.

  • 그리고 람다가 잘되는지 테스트해봅니다.
  • 테스트항목에 들어갑니다. 그리고 테스트 코드 넣는 곳에 해당 코들르 붙여넣습니다.
{
  "Records": [
    {
      "EventSource": "aws:sns",
      "EventVersion": "1.0",
      "EventSubscriptionArn": "arn:aws:sns:ap-northeast-2:981604548033:alarm-topic:test",
      "Sns": {
        "Type": "Notification",
        "MessageId": "test",
        "TopicArn": "arn:aws:sns:ap-northeast-2:123123:test-alarm-topic",
        "Subject": "ALARM: \"RDS-CPUUtilization-high\" in Asia Pacific (Seoul)",
        "Message": "{\"AlarmName\":\"TEST!!!\",\"AlarmDescription\":\"EC2 CPU 알람 (10% 이상 시)\",\"AlarmArn\":\"arn:aws:cloudwatch:ap-northeast-2:123123:alarm:ant-man-live-ALB-RequestCount-high\",\"AWSAccountId\":\"683308520328\",\"NewStateValue\":\"ALARM\",\"NewStateReason\":\"Threshold Crossed: 1 datapoint (10.0) was greater than or equal to the threshold (1.0).\",\"StateChangeTime\":\"2021-07-14T23:20:50.708+0000\",\"Region\":\"Asia Pacific (Seoul)\",\"OldStateValue\":\"OK\",\"Trigger\":{\"MetricName\":\"CPUUtilization\",\"Namespace\":\"AWS/EC2\",\"StatisticType\":\"Statistic\",\"Statistic\":\"MAXIMUM\",\"Unit\":null,\"Dimensions\":[{\"value\":\"i-0e3e982bf1c7f0910\",\"name\":\"EngineName\"}],\"Period\":300,\"EvaluationPeriods\":1,\"ComparisonOperator\":\"GreaterThanOrEqualToThreshold\",\"Threshold\":1.0}}",
        "Timestamp": "2021-06-07T10:51:39.536Z",
        "SignatureVersion": "1",
        "MessageAttributes": {}
      }
    }
  ]
}

  • 그리고 테스트버튼을 누르고 함수실행 결과가 성공인지 확인합니다.

  • 그리고 슬랙에 들어가서 자신의 채널에 위와같은 메시지가 오면 성공입니다.

  • 그리고 아래와 같은 메시지를 보내게 하게끔 설정인 트리거를 설정합니다.

  • 해당 트리거 구성은 SNS 그리고 주제는 본인이 만든 SNS 주제로 설정합니다.

  • 이렇게 설정되면 완성입니다!

위와 같이 설정이 완료되었고, 앞으로 이상 발생시, Slack으로 바로 알람이 갈겁니다.

5. 부하테스트

  • putty로 웹서버에 접속합니다.
  • 해당 명령어를 입력합니다.
sudo -i

amazon-linux-extras install epel -y

yum install stress -y

//설치 완료 후

stress -c 4 (프로세스 4개를 띄워서 서버 cpu 99% 찍는겁니다.
  • Slack에 가서 확인하시면

  • 이렇게 경보가 옵니다!
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/12   »
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
글 보관함