반응형

문제

root 권한이 없는 계정을 통해 ubuntu를 운용하면, code deploy시 생성된 코드의 directory가 'root'로 변경되어 code deploy가 실패하거나(code deploy 후, build 디렉토리 소유자가 root로 변경되었다.), 해당 디렉토리 내의 특정 작업들이 제한된다. 

이에 따라, codedeploy >> appspec.yml >> hooks 부분 script >> 해당 디렉토리에서 추가 작업(ex. log directory 생성)이 있을 경우, code deploy가 실패된다.

메시지
Script at specified location: ./script/start.sh run as user jane failed with exit code 1

왜?? start.sh에는 ecosystem.json을 호출하여 nodejs 코드를 띄워주는데 

ecosystem.json에서 error와 out log를 쌓는 디렉토리를 생성한다.

그런데, /home/jane/build 디렉토리 소유자는 root이므로, 'jane' 권한은 폴더를 만들 수 없기 때문에 실패한다.

 

원리

답은 service codedeploy-agent status에 있다.

이 코드는 아래 Loaded와 같이 /etc/init.d/codedeploy-agent에서 돌아가는데,

/etc/init.d/codedeploy-agent 소유자(그룹)는 2450, users다.

codedeploy-agent

하지만 이런 건 다른 데에도 분명 영향이 있어서 함부로 바꾸면 안된다.

구글링을 통해 찾자.

찾았다.

루트 프로파일 이외의 사용자 프로파일을 사용하여 CodeDeploy 에이전트를 실행하려면 어떻게 해야 하나요?

https://aws.amazon.com/ko/premiumsupport/knowledge-center/codedeploy-agent-non-root-profile/

 

루트 이외의 프로파일로 CodeDeploy 에이전트 실행

6.    다음 명령을 실행하여 어떤 프로세스가 실행 중이며, 어느 사용자가 해당 프로세스를 실행 중인지 확인합니다.

aws.amazon.com

 

해결

ubuntu@ip-192-168-23-61:/etc/init.d$ sudo service codedeploy-agent stop

 

// /etc/init.d/codedeploy/agent에 있는 ""를 "jane"으로 변경. (CodeDeploy agent를 수행할 non-root 계정)

ubuntu@ip-192-168-23-61:/etc/init.d$ sudo sed -i 's/""/"jane"/g' /etc/init.d/codedeploy-agent

 

// 변경 여부 확인. CODEDEPLOY_USER에 "jane"이 추가되었다.

ubuntu@ip-192-168-23-61:/etc/init.d$ vi /etc/init.d/codedeploy-agent

// /opt/codedeploy/agent에 있는 모든 파일들의 소유자/그룹을 jane:jane으로 변경한다.

ubuntu@ip-192-168-23-61:/usr/lib/systemd/system$ sudo chown jane:jane -R /opt/codedeploy-agent

ubuntu@ip-192-168-23-61:/usr/lib/systemd/systemsudo chown jane:jane -R /var/log/aws/

 

// 확인해보자. 진짜 다 바뀌었다.

ubuntu@ip-192-168-23-61:/usr/lib/systemd/system$ cd /opt/codedeploy-agent/

ubuntu@ip-192-168-23-61:/opt/codedeploy-agent$ ll

total 56

drwxr-xr-x 8 jane jane  4096 Jul  5 14:29 ./

drwxr-xr-x 4 root root  4096 Jul  4 15:35 ../

-r--r--r-- 1 jane jane    38 Apr 16  2021 .version

-rw-r--r-- 1 jane jane   353 Mar 23  2021 Gemfile

-rw-r--r-- 1 jane jane 10174 Mar 23  2021 LICENSE

drwxr-xr-x 2 jane jane  4096 Jul  4 15:35 bin/

drwxr-xr-x 2 jane jane  4096 Jul  4 15:35 certs/

-rw-r--r-- 1 jane jane  1158 Mar 23  2021 codedeploy_agent.gemspec

drwxr-xr-x 6 jane jane  4096 Jul  5 18:13 deployment-root/

drwxr-xr-x 4 jane jane  4096 Jul  4 15:35 lib/

drwxr-xr-x 3 jane jane  4096 Jul  4 15:35 state/

drwxr-xr-x 4 jane jane  4096 Jul  4 15:35 vendor/

 

jane@ip-192-168-23-61:/var/log$ cd aws

jane@ip-192-168-23-61:/var/log/aws$ ll

total 12

drwxr-xr-x  3 jane jane   4096 Jul  4 15:35 ./

drwxrwxr-x 10 root syslog 4096 Jul  6 15:25 ../

drwxr-xr-x  2 jane jane   4096 Jul  6 00:00 codedeploy-agent/

 

그리고 /etc/init.d의 codedeploy-agent.service 또한 User를 jane으로 입력해주자. 

(그렇지 않으면, 서비스는 여전히 root로 돈다)

 

이제 codedeploy-agent를 다시 실행하면 된다. (sudo는 빼자)

sudo service codedeploy-agent start
sudo service codedeploy-agent status

 

여기까지 하면, ps aux를 통해 codedeploy-agent가 띄워지는 권한이 root -> jane으로 변경된 것을 확인할 수 있다.

 

 

+ 그리고, appspec.yml에서 hook쪽에 runas가 있다면 이번엔 아래와 같은 에러가 발생한다.
[stderr]su: must be run from a terminal

 

이는 appspec.yml의 hooks 쪽에 있는 runas를 빼주면 된다

(runas가 실행권한과 다르면 상관없는데, agent를 동작시키는 권한과 같을 경우 에러가 발생한다)

 

반응형
반응형

지난 포스팅에 이어, 이번에는 Codedeploy 설정을 진행한다.

1. CodeBuild 설정

2. Codedeploy 설정: 이번 포스팅

3. EC2 Ubuntu 설정

 

 

Codedeploy에서 deploy에 사용할 애플리케이션(Jane-codedeploy)을 생성한다. 

플랫폼은 EC2/온프레미스를 설정하면 끝난다.

 

애플리케이션 생성 후엔 '배포 그룹'에서 실제로 이 code deploy가 어떤 인스턴스에 배포될지 설정한다.

나는 EC2에 배포를 할 것이고, EC2 중에서도 어떤 인스턴스에 배포할지는 인스턴스에 설정된 '태그'를 통해 코드가 배포된다.

(난 Service:Test 태그를 설정했다.)

codedeploy의 배포그룹 구성
코드를 배포할 ec2 인스턴스의 태그

 

 

그리고, codedeploy-agent를 ec2에 설치하면 된다.

나는 ubuntu용 ec2이므로 아래 링크를 참고했고, 들어가보면 윈도우 등 다양한 옵션이 있다.

 

* Codedeploy agent 설치

1. ruby 설치: Codedeploy agent는 ruby로 작성되었으므로 이를 설치한다.

$ sudo apt-get install ruby  // (ubuntu 16.04)

 

2. wget 설치 (agent 설치파일을 들고오기 위해 쓰인다)

$ sudo apt-get install wget

 

3. Codedeploy Agent 설치

# 나는 jane계정을 사용하므로 /home/jane이며, ubuntu 계정을 사용한다면 /home/ubuntu로 가면 된다.

$ cd /home/jane

 

# 설치파일 다운로드 (Seoul region) 

$ wget https://aws-codedeploy-ap-northeast-2.s3.ap-northeast-2.amazonaws.com/latest/install

 

# 실행권한 추가 및 설치

$ chmod +x ./install

$ sudo ./install auto

 

하다가 막힌다면 아래 aws docs를 참고한다.

https://docs.aws.amazon.com/ko_kr/codedeploy/latest/userguide/codedeploy-agent-operations-install-ubuntu.html

 

Ubuntu Server용 CodeDeploy 에이전트 설치 - AWS CodeDeploy

출력을 임시 로그 파일에 쓰는 것은 Ubuntu 20.04에서 install 스크립트를 사용하여 알려진 버그를 해결하는 동안 사용해야 하는 해결 방법입니다.

docs.aws.amazon.com

 

* Codedeploy agent 서비스 확인

$ sudo service codedeploy-agent status

* ubuntu 계정이 아닌, jane과 같이 별도의 root권한이 아닌 계정을 사용한다면 추가 설정이 필요하다. 

그렇지 않으면, codedeploy-agent가 root로 돌기 때문에(위 캡쳐본에 /etc/init.d/codedeploy/agent에서 돌고 있다),

코드가 배포되는 디렉토리가 root가 되어 추후 다른 문제들을 야기시킨다. 이는 별도로 포스팅 했으니 여기 참고!

https://countrymouse.tistory.com/entry/awscodedeployerr

 

[AWS] root 이외 계정에서 codedeploy 실행, codedeploy 'run as user jane failed with exit code 1', '[stderr]su: must be ru

문제 root 권한이 없는 계정을 통해 ubuntu를 운용하면, code deploy시 생성된 코드의 directory가 'root'로 변경되어 code deploy가 실패하거나(code deploy 후, build 디렉토리 소유자가 root로 변경되었다.),..

countrymouse.tistory.com

 

 

그리고, codedeploy 설정을 끝냈으니 codepipeline으로 돌아와서, '배포'부분에 codedeploy에서 만든 deploy를 추가해준다.

입력 아티팩트는 codebuild의 output이었던 BuildArifact이고, 애플리케이션은 codedeploy에서 만들었던 Jane-codedeploy,

배포 그룹은, jane-codedeploy에서 만들어줬던 jane-codedeploy이다.

 

 

그리고, codedeploy에는 appspec.yml이 필요한데, 이는 아래와 같이 작성했다.

appspec.yml

files: source, destination, runas

files은 source: / 내 파일들을 ec2의 destination( /home/jane/build/)로 옮긴다.

즉, build에서 산출된 artifacts의 모든 것들을 ec2의 /home/jane/build/로 옮긴다. (/home/jane/build/ 디렉토리는 생성시켜두자)

그리고, 그 동작은 'jane'이 run 한다.

 

permissions: 

복사한 파일들의 객체들은 모두(pattern: "**") owner/grp을 'jane'으로 준다.

근데, 이미 위에서 말한  "jane과 같이 별도의 root권한이 아닌 계정을 사용한다면 추가 설정이 필요하다. " 를 했으므로,

이 permission은 생략해도 된다.

 

hooks:

deploy는 아래 각 절차(hooks)마다 하나 이상의 스크립트를 통해 변수선언, 환경설정 등을 할 수 있는데

나는 간단한 코드를 올렸으므로 applicationStart에만 code를 실행시키는 간단한 event를 만들었다.

https://docs.aws.amazon.com/ko_kr/codedeploy/latest/userguide/reference-appspec-file-structure-hooks.html

 

start.sh

pm2로 nodejs 코드를 띄우는 sh이다. (pm2 reload ecosystem.json이 메인)

6번째 줄까지는 안 해도 된다. 나는 각 절차가 궁금해서 찍어봤다.

ecosystem.json

/home/jane/build 디렉토리에 있는 index.js를 실행한다. 

코드가 변경되는지 watch==true를 통해 확인하며, error_file, out_file이 쌓이는 위치를 지정해주었다.

 

이 모든 파일은(appspec,yml, scosystem.json, start.sh 등) 코드와 같이 git에 올리면 된다 :)

 

반응형
반응형

 

지난 번에 코드를 ECS Fargate로 배포하는 CI/CD pipeline을 구축했다.

이번에는 ECS Fargate가 아닌, EC2로 배포하는 CI/CD pipeline 구축을 정리해볼 예정이다.

(EC2를 사용하는 곳이 종종 있기 때문이다.)

 

1. CodeBuild 설정: 이번 포스팅

2. Codedeploy 설정

3. EC2 Ubuntu 설정

 

먼저, Codepipeline의 Source까지는 모두 이전 ecs cicd pipeline 포스팅과 동일하므로 생략한다.

* 이전 포스팅: https://countrymouse.tistory.com/entry/awscicd2

 

[AWS] CI/CD pipeline 구축 1. Bitbucket <-> Slack 연동

CI/CD 구축을 위한 단계 0. ECS fargate 생성 (Cluster, Service, task-definition, task) 1-1. Bitbucket에 코드 업로드 (git) 1-2. Bitbucket slack 연동 (PR 알람 노티를 위함) 2. Bitbucket AWS API Gateway..

countrymouse.tistory.com

 

그럼, Codebuild부터 시작해보자.

Codebuild는 별도 메뉴에서 생성하지 않았고, codepipeline를 만들 때 생성했다. (아래 프로젝트 생성)

입력아티팩트는, codepipeline의 source인 s3의 출력아티팩트 값과 동일해야 한다. 

codepipeline의 source부분의 '출력아티팩트'는 'SourceArtifact'로 설정되어 있다.

그리고, 출력아티팩트(BuildArtifact)는 이후 CodeDeploy의 입력아티팩트가 된다.

 

빌드에 사용할 소스와 빌드를 통해 산출되는 아티팩트는 각각 s3의 jane-bucket2에 저장된다.

그리고, build하는 데에 사용되는 buildspec.yml이 필요하다.

 

 

buildspec에는 code deploy에서 배포할 환경이 ubuntu이므로 별도의 docker 작업은 포함되지 않고

n 16.14.0 환경에서 빌드를 수행하게 도와준다.

  • n 은 우분투의 node.js 버전관리 플러그인

 

node_modules

그리고, 소스코드에선 node_modules를 포함하지 않았는데(무거워지니까), codebuild에서 npm install을 통해

코드 실행에 필요한 node_modules들을 생성시켜 준다.

Artifacts

그리고, 해당 빌드를 통해 저장할 파일들을 artifacts: files:에 넣어주는데, 보통 '**/*'로 전체를 artifacts(결과물)로 만들어준다.

(이 artifacts는 codebuild에 있었던 출력아티팩트 저장 위치(s3:jane-bucket2/jane-codepipeline7/BuildAritf/~에 업로드 된다.)

 

Cache

앞으로 해당 코드를 또 수행할 경우, cache를 해 두면 빌드 시간이 굉장히 줄어든다. 그래서 캐시를 전체를 설정한다.

version: 0.2

phases:
install:
commands:
- n 16.14.0
- echo Build starting on `date`
- node --version
build:
commands:
- npm install
- pwd
artifacts:
files:
- '**/*'
cache:
path:
- '**/*'
 

 

buildspec.yml은 다른 코드들과 동일하게 git에 같이 올려서(가장 상위 디렉토리에),

git pr merge가 발생할 때마다 api gateway -> lambda -> s3 를 통해 codebuild가 수행하며 사용된다.

 

다음 포스팅은 상당히 애 먹었던 codedeploy다.

반응형
반응형

상황

AWS의 lambda 안에서 git 명령을 사용하여(execSync를 이용) Run하면,

아래와 같이 '/bin/sh: git: command not found'가 발생한다.

 

2022-07-04T04:21:23.997Z a8ab8b4b-6f54-4775-90a9-1076ba630dc8 INFO proxy: undefined
/bin/sh: git: command not found

 

해결

람다 메인에서 계층에 'Add a layer'로 lambda layer를 추가해준다.

단, 해당 람다와 동일한 리즌에 있는 계층과 연결해야 하므로

아래 github 링크에서 언급한 arn의 리즌을 us-east-1 --> ap-northeast-2(서울)로 바꿔주고 저장한다.

 

 

 

출처

https://github.com/lambci/git-lambda-layer

 

GitHub - lambci/git-lambda-layer: A layer for AWS Lambda that allows your functions to use `git` and `ssh` binaries

A layer for AWS Lambda that allows your functions to use `git` and `ssh` binaries - GitHub - lambci/git-lambda-layer: A layer for AWS Lambda that allows your functions to use `git` and `ssh` binaries

github.com

 

반응형
반응형

Cloud Scheduler: GCP의 Cloud function에 올려둔 코드를 주기적으로 호출해보자.

주기적인 호출(=cron)을 위해서는 GCP의 Cloud schduler 앱을 사용한다.

나는 10분에 한 번씩 cloud function에 있는 특정 함수가 호출되도록 cloud scheduler에서 작업을 추가했다.

 

1. GCP > Cloud Scheduler > 작업 만들기

10분에 한 번씩 cloud function에 있는 특정 함수가 실행되도록 할 것이므로 빈도를 */10으로 설정해준다.

URL은, cloud function에서 만들었던 함수의 trigger되는 URL이다.

(== 내가 Trigger 하고 싶은 cloud function의 특정 함수 URL)

 

 

HTTP 헤더(Content-Type, User-Agent)는 해당 task를 저장하면 자동으로 입력되는 값이다.

인증헤더는 일반적으로 'OIDC 토큰 추가' 를 사용한다. 

https://cloud.google.com/scheduler/docs/http-target-auth?hl=ko 

 

HTTP 대상에 인증 사용  |  Cloud Scheduler 문서  |  Google Cloud

의견 보내기 HTTP 대상에 인증 사용 적절한 사용자 인증 정보가 있는 연결된 서비스 계정을 설정한 경우 Cloud 스케줄러는 인증이 필요한 HTTP 대상을 호출할 수 있습니다. 서비스 계정 설정 Cloud Sche

cloud.google.com

 

그렇게 scheduler를 생성 후 '다음 실행' 동안 기다려보면, scheduler를 통해 함수가 실행되는 것을 확인할 수 있다.

반응형
반응형

* Cloud Function: GCP의 이벤트 기반 서버리스 플랫폼 (= AWS Lambda)

 

오늘은 지난 번에 만든 airflow 특정 DAG의 running 시간을 체크하는 코드를 GCP의 cloud function에서 돌려본다.

언어는 NodeJS를 사용하고, 파일은 index.js, package.json, markdown.json을 사용한다.

이전코드: https://countrymouse.tistory.com/entry/airflowdag

 

Airflow에서 특정 DAG의 실행 시간을 현재시간과 비교하여 slack으로 메시지를 보내주는 코드.

배경 GCP의 airflow에서 특정 DAG의 마지막 batch 시간 start_time을 '현재 시간'과 비교해서, 30분이상 차이가 날 경우, slack으로 알람을 보내줄 것이다. (30분이상 차이가 난다면 배치가 제대로 돌고 있지

countrymouse.tistory.com

 

* GCP cloud function은, AWS lambda와는 다르게 node_modules를 별도로 첨부하지 않는다.

그냥 코드만 올려두고 배포를 실행하면, GCP가 알아서 빌드를 하고 node_modules를 내부적으로 만들어준다.

(그래서 빌드 시간이 더 오래 걸림)

 

 

나는 이전 포스팅의 코드를 그대로 사용하며,

serverless는 이벤트에 의해 코드가 Trigger 되기 때문에 exports handler를 사용한다. 

함수는 export handler 안에서 main을 호출하고 200을 보낸 후 종료된다.

exports.start = async (req, res) => {
await main();
res.send(200);
}

 

그렇게 코드를 배포 후, 로그를 확인해보면 Function이 정상 started 되고 로그도 잘 찍힌 것을 볼 수 있다.
 
반응형
반응형

배경

GCP의 airflow에서 특정 DAG의 마지막 batch 시간 start_time을 '현재 시간'과 비교해서,

30분이상 차이가 날 경우, slack으로 알람을 보내줄 것이다. (30분이상 차이가 난다면 배치가 제대로 돌고 있지 않다는 것이므로)

 

 

구현

1. 일단 airflow에서 값을 가져오기 위한 RestAPI를 찾아보자. (버전은 1.10.3이다)

https://airflow.apache.org/docs/apache-airflow/1.10.3/api.html

원하는 API가 보인다. 각 DAG들의 latest DagRun을 리턴해주는 API. 이걸 사용해볼 예정이다.

 

 

2. API로 데이터 가져오기

일단 axios.get으로 API(http://airflow.~.com/api/experimental/latest_runs)를 쏘고, 응답을 확인해본다.

헤더에는 accountAuth 값을 포함해야 하는데, 이는 id:pw를 base64로 인코딩한 값이다.

코드

async function getDagInfoList(siteUri){
const res = await axios.get(siteUri, {
headers: {
Authorization: `Basic ${accountAuth}`
}
})
const dagInfoList = res;
console.log(dagInfoList);
return dagInfoList;
}

 

응답

저기 data:items 부분을 가져오면 될듯하다. data dagInfoList를 res.data로 수정하여 다시 가져와보자.

jane@HMH73K0H9Y jane-repo % node airflow.js
실행
{
  status: 200,
  statusText: 'OK',
  headers: {
    server: 'gunicorn/19.10.0',
    date: 'Sun, 19 Jun 2022 09:21:00 GMT',
    'content-type': 'application/json',
    'content-length': '6248',
    via: '1.1 google',
    connection: 'close'
  },
  config: {
    transitional: {
      silentJSONParsing: true,
      forcedJSONParsing: true,
      clarifyTimeoutError: false
    },
    adapter: [Function: httpAdapter],
    transformRequest: [ [Function: transformRequest] ],
    transformResponse: [ [Function: transformResponse] ],
중략
      'user-agent': [Array],
      host: [Array]
    }
  },
  data: {
    items: [
      [Object], [Object], [Object],
      [Object], [Object], [Object],
      [Object], [Object], [Object],
      [Object], [Object], [Object],
      [Object], [Object], [Object],
      [Object], [Object], [Object],
      [Object], [Object], [Object],
      [Object], [Object], [Object],
      [Object], [Object]
    ]
  }
}

 

변경 코드 부분

const dagInfoList = res.data;

 

응답

해당 site에 등록된 모든 DAG의 latestRun값의 items 값이 출력된다.

내가 사용할 값은 dag_id=b의 start_date이기 때문에 res를 더 깊은 res.data.items로 수정한다.


jane@HMH73K0H9Y jane-repo % node airflow.js
실행
{
  items: [
    {
      dag_id: 'a',
      dag_run_url: '/admin/airflow/graph?dag_id=a&execution_date=2022-01-03+00%3A00%3A00%2B00%3A00',
      execution_date: '2022-01-03T00:00:00+00:00',
      start_date: '2022-01-04T03:44:50.593295+00:00'
    },
    {
      dag_id: 'b',
      dag_run_url: '/admin/airflow/graph?dag_id=b&execution_date=2022-05-05+18%3A30%3A00%2B00%3A00',
      execution_date: '2022-05-05T18:30:00+00:00',
      start_date: '2022-06-05T18:30:02.388330+00:00'
    },
    {
      dag_id: 'c',
      dag_run_url: '/admin/airflow/graph?dag_id=c&execution_date=2022-06-19+09%3A10%3A00%2B00%3A00',
      execution_date: '2022-06-19T09:10:00+00:00',
      start_date: '2022-06-19T09:15:01.873317+00:00'
중략

 

변경 코드

const dagInfoList = res.data.items;

 

응답

jane@HMH73K0H9Y jane-repo % node airflow.js 
실행
[
  {
    dag_id: 'a',
    dag_run_url: '/admin/airflow/graph?dag_id=a&execution_date=2022-01-03+00%3A00%3A00%2B00%3A00',
    execution_date: '2022-01-03T00:00:00+00:00',
    start_date: '2022-01-04T03:44:50.593295+00:00'
  },
  {
    dag_id: 'b',
    dag_run_url: '/admin/airflow/graph?dag_id=b&execution_date=2022-05-05+18%3A30%3A00%2B00%3A00',
중략

 

3. 내가 원하는 부분의 start_data만 추출하기

위에서 저장한 dagInfoList에서 targetDagId를 찾는 함수다. targetDagId는 전역변수로 미리 설정되어 있으며,

나는 a라는 DagId의 start_date만 필요하기 때문에 이를 return 값으로 가져온다.

const targetDagId = 'a';
function findTargetDagStartTime(dagInfoList, targetDagId){
const TargetDagInfo = dagInfoList.find((daginfo) => daginfo.dag_id === targetDagId);
const startTime = TargetDagInfo.start_date;
return startTime;
}

 

4. timegap 계산 (분 단위 계산)

현재 시간과 target시간을 각각 생성하여 현재 시간에서 target시간을 빼준다.

그리고 이는 정수값이기 때문에 /1000/60을 해서 분 단위의 gap을 찾는다. 여기서 초 단위를 하려면 /60대신 /3600을 하면 된다.

function calculateTimeGap(targetDagStartTime){
const now = new Date();
const TargetTime = new Date(targetDagStartTime);
console.log(`CurrentTime(KST): ${now}`);
console.log(`Target_Time(KST): ${TargetTime}`);

const timeGap = now.getTime() - TargetTime.getTime();
const minTimeGap = timeGap / 1000 / 60;
console.log(`* minTimeGap: ${minTimeGap}\n`);
return minTimeGap;
}

 

5.  timegap이 30분보다 크면 slack으로 메세지 전송

function messageToSlack(uri, minTimeGap){
webhook.send({
text: `${uri}]\n* minTimeGap: ${minTimeGap}\n`
})
}


// maincode
async function start(uri){
const dagInfoList = await getDagInfoList(uri);
console.log('Site: ' + uri);

const targetDagStartTime = findTargetDagStartTime(dagInfoList, targetDagId);
const minTimeGap = calculateTimeGap(targetDagStartTime);

if (minTimeGap > 30) messageToSlack(uri, minTimeGap);
}

 

6.  cron으로 매 초마다 timegap 체크

/*
* test
*/

cron.schedule('* * * * *', () => {
console.log('실행');
start(airflowabcUri); // 나는 두 airflow 사이트 값이 필요해서 두 개의 uri를 전역변수로 만들어줬다.
start(airflowdefUri);
});

 

7. 전체 코드 

const cron = require('node-cron');
const axios = require('axios');
const accountAuth = 'abcdefg';   // base64로 id:pw값을 인코딩해서 넣어줘야 한다.
const targetDagId = 'a';

const { IncomingWebhook } = require('@slack/webhook');
const slackUri = 'https://hooks.slack.com/services/abcdefg';  // webhook 생성 후 나오는 uri
const webhook = new IncomingWebhook(slackUri);

/************************************
* function
*************************************/

async function getDagInfoList(siteUri){
const res = await axios.get(siteUri, {
headers: {
Authorization: `Basic ${accountAuth}`
}
})
const dagInfoList = res.data.items;
console.log(dagInfoList);
return dagInfoList;
}

function findTargetDagStartTime(dagInfoList, targetDagId){
const TargetDagInfo = dagInfoList.find((daginfo) => daginfo.dag_id === targetDagId);
const startTime = TargetDagInfo.start_date;
return startTime;
}

function calculateTimeGap(targetDagStartTime){
const now = new Date();
const TargetTime = new Date(targetDagStartTime);
console.log(`CurrentTime(KST): ${now}`);
console.log(`Target_Time(KST): ${TargetTime}`);

const timeGap = now.getTime() - TargetTime.getTime();
const minTimeGap = timeGap / 1000 / 60;
console.log(`* minTimeGap: ${minTimeGap}\n`);
return minTimeGap;
}

function messageToSlack(uri, minTimeGap){
webhook.send({
text: `${uri}]\n* minTimeGap: ${minTimeGap}\n`
})
}

async function start(uri){
const dagInfoList = await getDagInfoList(uri);
console.log('Site: ' + uri);

const targetDagStartTime = findTargetDagStartTime(dagInfoList, targetDagId);
const minTimeGap = calculateTimeGap(targetDagStartTime);

if (minTimeGap > 30) messageToSlack(uri, minTimeGap);
}

/************************************
* test
*************************************/

cron.schedule('* * * * *', () => {
console.log('실행');
start(airflowAUri);
start(airflowBUri);
});

 

그럼 실행결과는 이렇게 된다.

jane@HMH73K0H9Y jane-repo % node airflow.js
실행
Site: http://airflow.abc.com/api/experimental/latest_runs
CurrentTime(KST): Sun Jun 19 2022 18:52:01 GMT+0900 (대한민국 표준시)
Target_Time(KST): Sun Jun 19 2022 18:47:03 GMT+0900 (대한민국 표준시)
* minTimeGap: 4.967416666666667


Site: http://airflow.def.com/api/experimental/latest_runs
CurrentTime(KST): Sun Jun 19 2022 18:52:01 GMT+0900 (대한민국 표준시)
Target_Time(KST): Sun Jun 19 2022 18:47:01 GMT+0900 (대한민국 표준시)
* minTimeGap: 4.991966666666666

^C

 

slack으로 webhook은 이렇게 들어온다. (gap이 30이 넘을 시에만 메세지가 오게 만들었으나, test를 위해 찍어보았다)

 

반응형
반응형

Docker-compose 배경 설명

일반적으로 prometheus나 Grafana를 설치한다고 하면, docker full로 이미지를 땡겨와서 설치하는 게 주된 방법이다.

하지만, container가 다수 개가 될 경우 관리가 어려워진다.

 

그래서, Docker-compose를 통해 하나의 docker-compose.yml로 여러 개의 컨테이너를 동시에 생성/관리/삭제할 수 있다.


나는 Docker-compose를 통해 Prometheus + Grafana를 실행할 예정으로, 아래와 같이 Docker.compose.yml를 작성했다.

 

크게 보면, prometheus와 grafana간 통신 가능하도록 하나의 networks를 구성해서 묶어주었고 (networks: monitor),

컨테이너의 /etc/prometheus/ 파일들을 volumes으로 로컬 /home/monitor/prometheus/ 로 마운트 시켜주었다.

  1 version: '3.8'
  2 networks:
  3   monitor:
  4     driver: bridge
  5 
  6 services:
  7   prometheus:
  8     image: prom/prometheus:latest
  9     container_name: prometheus
 10     user: root
 14     volumes:
 15       - /home/monitor/prometheus/:/etc/prometheus/
 16       - /home/monitor/prometheus/data:/prometheus
 17     ports:
 18       - 9090:9090
 19     networks:
 20       - monitor
 21     restart: always
 22   grafana:
 23     container_name: grafana
 24     image: grafana/grafana:latest
 25     environment:
 26       - GF_SECURITY_ADMIN_USER=admin
 27       - GF_SECURITY_ADMIN_PASSWORD=test1234
 28       - GF_USERS_ALLOW_SIGN_UP=false
 29     volumes:
 30       - /home/monitor/grafana:/var/lib/grafana
 31       - /home/monitor/grafana/provisioning:/etc/grafana/provisioning
 32     ports:
 33       - 3000:3000
 34     depends_on:
 35       - prometheus
 36     networks:
 37       - monitor
 38     restart: always

 

그리고, prometheus.yml를 작성한다. 

최소 동작을 위해 필요한 설정들은 딱히 많지 않다. targets만 적어주면 된다 (=exporter가 설치된 노드 ip)

 

 

 

이제 준비해야할 파일은 끝났다. docker-compose를 실행해보자.

docker-compose up // 컨테이너 및 network 생성 및 실행

[ec2-user@ monitor]$ sudo docker-compose up -d         // d: background에서 실행

Creating network "monitor_monitor" with driver "bridge"

Creating prometheus ... done

Creating grafana    ... done

[ec2-user@ monitor]$ 

[ec2-user@ monitor]$ sudo docker ps          

CONTAINER ID   IMAGE                    COMMAND                  CREATED         STATUS         PORTS                                       NAMES

34c0e6778103   grafana/grafana:latest   "/run.sh"                3 minutes ago   Up 3 minutes   0.0.0.0:3000->3000/tcp, :::3000->3000/tcp   grafana

85878fe66a18   prom/prometheus:latest   "/bin/prometheus --c…"   3 minutes ago   Up 3 minutes   0.0.0.0:9090->9090/tcp, :::9090->9090/tcp   prometheus

 

포트포워딩으로 웹 접속이 가능한지 확인

 

$ ssh -i [인증서].pem -p [local port] ec2-user@[ec2 public IP] -L 9090:[Prometheus가 떠 있는 ec2]:9090

$ ssh -i [인증서].pem -p [local port] ec2-user@[ec2 public IP]  -L 3000:[Grafana가 떠 있는 ec2]:3000

 

1. Prometheus: localhost:9090/

2. Grafana: localhost:3000/

+ 포트 포워딩은 아니지만 curl을 통해 node exporter가 설치된 node 확인.

1. 프로메테우스
2. grafana

+ node exporter의 metrics 확인

[ec2-user@ip-100-64-50-187 monitor]$ curl http://[Node exporter가 설치된 target IP]/metrics
# HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.
# TYPE go_gc_duration_seconds summary
go_gc_duration_seconds{quantile="0"} 2.5627e-05
go_gc_duration_seconds{quantile="0.25"} 5.0424e-05
go_gc_duration_seconds{quantile="0.5"} 8.6099e-05
go_gc_duration_seconds{quantile="0.75"} 0.000109742
go_gc_duration_seconds{quantile="1"} 0.000267627
go_gc_duration_seconds_sum 14.640208971
go_gc_duration_seconds_count 161529
# HELP go_goroutines Number of goroutines that currently exist.

완료.

 

여기서 prometheus를 data source로 가져와보자.

URL은 prometheus의 dns name인 prometheus를 가져오고, port를 지정해주면 된다.

(localhost는 안된다. 서로 다른 컨테이너니까.)

 

그리고 grafana Labs에 있는 dashboard를 하나 import 해보자. URl을 그대로 복붙해도 된다.

https://grafana.com/grafana/dashboards/15172

 

그렇게 하면 이런 대시보드가 생성되어 prometheus로부터 받는 데이터를 예쁘게 보여준다.

 


 

docker-compose로 한 번에 생성시킨 컨테이너들을 중단하는 방법은,

docker-compose down // 컨테이너 및 network 정지 및 삭제

* docker-compose down는 컨테이너만 정지시킨다.

[ec2-user@ip- monitor]$ sudo docker-compose down

Removing grafana    ... done

Removing prometheus ... done

Removing network monitor_monitor

[ec2-user@ip- monitor]$ 

[ec2-user@ip- monitor]$ sudo docker ps

CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

[ec2-user@ip- monitor]$ 

반응형
반응형

CI/CD 구축을 위한 단계

  0. ECS fargate 생성 (Cluster, Service, task-definition, task)

1-1. Bitbucket에 코드 업로드 (git)

1-2. Bitbucket <-> slack 연동 (PR 알람 노티를 위함)

2-1. Bitbucket <-> AWS API Gateway 연동 with Webhook

2-2. API gateway -> Lambda 호출

3. Lambda -> S3로 패키지 업로드

4. AWS Codepipeline: S3 -> Codebuild + ECR 연동

5. Codebuild -> Deploy(ECS fargate)

6. ECS <-> LB(ALB) 연동하여 동작 테스트


다 왔다. 이제 마지막 단계인 Deploy 단계이다.

이전 포스팅 (4. Codebuild + ECR 연동)에서 빌드 스테이지까진 만들어 두었으므로, 이번엔 배포 스테이지를 만들어 본다 :)

 

 

이전 포스팅에서 만들어 두었던 코드 파이프라인을 편집해서 Deploy 작업 그룹을 추가한다.

 

작업공급자는, Codedeploy가 아닌 Amazon ECS를 사용한다.

왜냐면, ECS의 fargate에 배포를 할 거고, container를 관리해주는 서비스 자체가 ECS이므로.

 

그래서, Deploy의 입력 아티팩트는, Build에서 저장했던 imagedefinitions.json이 되므로 BuildArtifact로 설정한다.

그리고 클러스터 및 서비스는 이 포스팅 0편에서 다뤘던, ECS의 클러스터/서비스를 선택한다.

이미지 정의 파일은 codebuild에서 buildspec.yml으로 언급한 imagedefinitions.json으로 설정한다.

 

그럼 codepipeline 작업은 다 끝났다.

이제 소스코드가 정말 pr merge 되었을 때 cicd pipeline이 동작하는지 테스트를 해보자.

 

 

1. bitbucket에서 PR Merge를 호출한다.

 

2. slack으로 PR merge에 대한 webhook이 수신된다.

 

 

3. API gateway -> Lambda -> S3에 source 코드가 업로드 되고, Build가 진행된다.

 

 

 

4. Codebuild가 완료된다.

 

 

5. Deploy가 진행된다.

 

 

ECS 컨테이너로 와 보면, 기존엔 컨테이너가 1개 였는데 2개가 되었다가, 다시 1개가 된다.

 

 

해당 ECS의 서비스에서 이벤트를 봐보면,  새로운 task가 실행된 것이다.

(08:38:35, service Jane-service2 has started 1 tasks task d0~~)

 

롤링 업데이트를 포스팅 0번에서 설정했기 때문에,

새로운 컨테이너가 띄워지고 새 컨테이너가 정상 기동되면 이전 컨테이너가 종료된다.

 

그렇게 Deploy까지 완료되고, slack에서 aws chatbot을 통해 결과까지 받아보았다.

이 결과에 대한 리포트는, 코드 파이프라인의 '알림 규칙 생성'에서 만들 수 있다.

 

Succeeded와 Failed만 선택해야 알람이 깔끔하게 온다.

started까지 선택하면, 절차마다 started 라는 알람이 한 번 더 오기 때문에

그냥 결과에 대해서만(succeeded, failed) 설정하는 게 깔끔하다.

 

 

여기까지 ci/cd Pipeline 구축 완료!

반응형
반응형

CI/CD 구축을 위한 단계

  0. ECS fargate 생성 (Cluster, Service, task-definition, task)

1-1. Bitbucket에 코드 업로드 (git)

1-2. Bitbucket <-> slack 연동 (PR 알람 노티를 위함)

2-1. Bitbucket <-> AWS API Gateway 연동 with Webhook

2-2. API gateway -> Lambda 호출 

3. Lambda -> S3로 패키지 업로드

4. AWS Codepipeline: S3 -> Codebuild + ECR 연동

5. Codebuild -> Deploy(ECS fargate)

6. ECS <-> LB(ALB) 연동하여 동작 테스트 


이제 AWS의 Codepipeline을 사용하여 ci/cd pipeline을 만들어보자.

일단 개요를 만드는데, 이 파이프라인에 대한 로그, 아티팩트(결과물)을 남길 위치를 설정한다(고급설정).

나는 소스 코드가 있는 버킷을 아티팩트가 저장될 버킷으로 설정했다.

(어차피 버킷 내 경로는 다르므로)

 

그리고 소스 스테이지를 추가 하는데, s3에서 소스코드를 가져오므로 S3를 소스 공급자로 선택하고,

버킷은 소스코드가 올라가 있는 jane-s3-cicd로 설정한다.

그리고, 객체 키는 lambda에서 s3로 패키지를 업로드 시킬 때 사용했던 파일명을 가져온다. (사진은 임의로 입력한 값)

 // Key: `${repoConfig.projectName}/${repoConfig.repoName}/${repoConfig.branch}.zip`

 

 

이제 빌드 스테이지를 추가해야 하는데, 이건 Codebuild에서 생성할 수 있으므로 Codebuild로 넘어가보자.

 

기본 구성을 만든 후

 

 

소스는 아까 codepipeline에서 언급했던 것처럼, 소스코드가 담겨있는 공급자인 S3, jane-s3-cicd로 설정한다.

 

환경은 어차피 빌드를 할 환경이므로 codebuild에서 제공하는 이미지로 설정한다.

환경은 어차피 빌드를 할 환경이므로 codebuild에서 제공하는 이미지로 설정한다.

 

그리고 build 하는 데에 필요한 buildspec을 정의한다. (아래에서 언급)

 

 

아티팩트(결과물)은 ECR에 저장 + codepipeline으로 다시 이 코드빌드를 가져올 것이기 때문에 아티팩트 없음으로 둔다.

그렇게 빌드 프로젝트를 생성하고, 다시 codepipeline으로 돌아온다.

 

 

그러면, 이제 빌드 스테이지에서 AWS Codebuild를 선택하고, 생성했던 프로젝트 이름을 선택해서 배포 스테이지로 넘어간다.

 

배포 스테이지는 잠깐 건너뛰기 해놓고, 빌드 스테이지를 좀 더 보도록 하자. 

 

빌드 스테이지에서는 크게 두 가지 작업을 해 주어야 한다.

1) ECR(Elastic Container Repository) 생성: build 후 docker image가 저장될 저장소. deploy시 사용됨 

2) buildspec.yml: 빌드 과정을 정의해 놓은 파일

 

먼저 이미지가 저장될 ECR 부터 확인해보자.  (ECR은 ci/cd pipeline 포스팅 0번, ECS 생성에서 이미 만들었다.)

나는 아래 리포지토리를 쓸 것이기 때문에 아래 jane-n-repo 이름을 buildspec.yml에 넣으면 된다.

 

 

buildspec은 크게 install, pre_build, build, post_build 절차로 나뉜다. 

즉, 소스코드를 설치할 때 하는 작업들을 install: commands: 에서 해주고 (ECR 로그인 등)

빌드할 때는 docker build -t로 tagging을 해 주고,

빌드가 끝난 다음에는, docker image를 ECR로 push하는 절차들을 담고 있다.

 

artifacts는 이 build가 끝나면 남는 결과물을 저장하는 것인데, imagedefinitions.json에 위 post_build의 echo값

{name:IMAGE_NAME, imageUri: ECR_PATH:latest} 값이 저장된다.

이 값은 나중에 deploy를 할 때 참고하는 값이 된다.

 

그래서, 이 buildspec.yml까지 bitbucket에 함께 올려놔서, s3로 함께 업로드 시켜두면 된다 :)

그러면, 이제 소스코드가 s3로 업로드 될 때마다 소스 스테이지를 거쳐 빌드 스테이지를 진행하게 되고, 

로그가 남게 된다 :)

 

아까 codepipeline의 아티팩트를 s3에 저장되게 설정했으므로, s3에 해당 codepipeline의 이름을 가진 디렉토리가 생성되었다.

SourceArti/ 는 소스 스테이지에서 사용한 파일들이고,

BuildArtif/는 빌드 후 추출된 artifacts (buildspec.yml에서 설정되어 있던)가 저장되어 있다.

 

실제로 BuildArtif/를 열어보면, zip파일이 있는데

빌드를 할 때마다 생성되며, 아래와 같이 buildpsec.yml에서 언급한 imagedefinitions.json이 남아있다.

 

아래는 SourceArti/ 에 남은 소스 코드들이다. bitbucket에서 다운받은 파일들이 모두 들어가 있다.

 

여기까지 빌드 끝!

다음은 5. Codebuild -> Deploy(ECS fargate) 이다 :)

반응형

+ Recent posts