안녕하세요 깍돌이입니다.

 

기존 포스팅에 이어서 

 

바로 다음 포스팅에서는 최대워커 수만큼 호출시 와 지금 은 함수를 return new Promise로 해서 Promise.all 로 받았지만

사실 진짜 원하는건 멀티스레드아니였나 

A , B함수에서A가 계속 루프돌고 CPU 연산을 하면 B는 실행도 못하는게 실행되는게 아니였나? 

라는 저만의 질문에 답을 하기 위한 포스팅입니다.

일단 다시 한번 제가 하려고했던 기능을 정리하겠습니다.

A 함수

0.2초마다 A라는 배열을 채웁니다.

B 함수

0.2초마다 A라는 배열에 값이 차면 꺼내와서 테스트 플랫폼에 해당 내용으로 자동화를 요청합니다.

기존 포스팅에서 return new Promise로 했었기 때문에 사실 멀티스레드를 흉내내는지 궁금했습니다.

5개의 함수가 각각 CPU 연산을 할 경우 어떻게 되는가? 입니다.

 

그럼 실제 연산을 해야되니까 

 

워커 코드를 아래와같이 변경하였습니다.

Worker.js

'use strict'

// 테스트 용 
let memoryArray = [];

let worker ={
  today_resource_filtering,
  filtered_resource_request_automation,

  today_resource_filtering_node_fn,
  filtered_resource_request_automation_node_fn,

  dummy_fn,
}

let g_closeCondition =100000000;

function today_resource_filtering_node_fn(){
      let i=0;
      let calculatorNumber = 0;
      console.time('today_resource_filtering_node_fn');
      while(true && i < g_closeCondition){
        i++;
        calculatorNumber+=i;
      }
      console.timeEnd('today_resource_filtering_node_fn');
      console.warn(calculatorNumber);
}

function filtered_resource_request_automation_node_fn(){
      let i=0;
      let calculatorNumber = 0;
      console.time('filtered_resource_request_automation_node_fn');
      while(true && i < g_closeCondition){
        i++;
        calculatorNumber+=i;
      }
      console.timeEnd('filtered_resource_request_automation_node_fn');
      console.warn(calculatorNumber);
}

function dummy_fn(name){
  let increNumber=0;
  console.warn(`========Start ${name}`);
  for(let i=0;i<g_closeCondition*50;i++){
      increNumber+=i;
  }
  console.warn(`========End Dummy Fn ${name}`);

}





module.exports = worker;

 

Node.js ( basic ) 

단순 호출하는 Node.js 입니다. 

let worker = require('./filter_and_request.js');

console.time('using_basic_js');

// 1억 까지 더하는 함수 
console.warn('---------------------Start Today Resource node fn')
worker.today_resource_filtering_node_fn();
console.warn('\r\n');

// 1억 까지 더하는 함수 
console.warn('---------------------Start filtered_resource_request_automation node fn')
worker.filtered_resource_request_automation_node_fn();
console.warn('\r\n');

// 50억 까지 더하는 함수 5번 호출 
for(let i=0;i<5;i++){
  console.warn(`-------------Start dummy calculator fn ${i+1}`);
  worker.dummy_fn(i+1);
  console.warn('\r\n');
}

console.timeEnd('using_basic_js');

 

 

Piscina 사용

const path = require('path');
const Piscina = require('piscina');

const piscina = new Piscina({
  filename:path.resolve(__dirname,'filter_and_request.js')
});

console.time('using_Piscina_js');

// 1억 까지 더하는 함수 
console.warn('---------------------Start Today Resource node fn')
piscina.run({},{name:"today_resource_filtering_node_fn"});
console.warn('\r\n');

// 1억 까지 더하는 함수 
console.warn('---------------------Start filtered_resource_request_automation node fn')
piscina.run({},{name:"filtered_resource_request_automation_node_fn"})
console.warn('\r\n');

// 50억 까지 더하는 함수 5번 호출 
for(let i=0;i<5;i++){
  console.warn(`-------------Start dummy calculator fn ${i+1}`);
  piscina.run(i+1,{name:"dummy_fn"});
  console.warn('\r\n');
}

console.timeEnd('using_Piscina_js');

 

dummy_fn 의 시간을 재기 위해 console.time을 추가했습니다.

function dummy_fn(name){
  let increNumber=0;
  console.warn(`========Start ${name}`);
  console.time(`Check Time ${name}`)
  for(let i=0;i<g_closeCondition*50;i++){
      increNumber+=i;
  }
  console.warn(`========End Dummy Fn ${name}`);
  console.timeEnd(`Check Time ${name}`)

}

 

결과

Node.js 호출  ( 순차적으로 실행되며 -  57초 발생

밑에 57초라는게 결국 console.time에서 console.timeEnd가 만나는 시점에 결정되는건데 결국 앞단의 함수들이 CPU 를 집약적으로 사용하고있으면 뒤에 함수는 실행도 못하고 대기하고 있다는 뜻이 됩니다. 

100ms + 100ms + 5s + 5s + 15s + 15s + 15  = 대략 57000 = 57초  ( 동기식 ) 

Piscina 호출 

일단 코드 Blocking되는 부분이 없습니다. using_Piscna_js가 6ms입니다 ( 6s아니고 6s  0.06초입니다. )

이부분을 보면 테스트를 해야하는 코드는 바로 실행하고 넘어갔음을 알수 있습니다.

 

이로써 Piscina가 worker thread 를 정상적으로 잘 쓰고있는지 정말 worker thread pool 은 기존의 노드 코드를 Blocking하지 않는지 직접 확인해봤는데요 

 

마지막 포스팅은 제가 원래 하려고했던 작업 ( 하나의 배열을 한쪽은 계속 필터링해서 넣고 -> 한쪽은 배열 차면 꺼내서 테스트 자동화 플랫폼에 테스트 요청을 보내는 ) 에 대한 코드를 올리겠습니다.

 

 

 

 

안녕하세요 오랜만에 글을 쓰게 되는데요 

 

여기서는 궁금증을 다 풀어갈 예정입니다.

 

목적부터 말씀 드리면

 

A 함수

0.2초마다 A라는 배열을 채웁니다.

B 함수

0.2초마다 A라는 배열에 값이 차면 꺼내와서 테스트 플랫폼에 해당 내용으로 자동화를 요청합니다.

 

문제점

2개의 함수 모두 계속 loop을 돌면서 진행하게되는데 배열을 채우는 A함수는 지속적인 CPU I/O 가 발생하게 됩니다. 

Node.js에서는 기본적으로는 멀티스레드가 안되기 때문에 저번 포스팅에서 Piscina를 사용하는 예제를 보여드렸었습니다.

 

그래서 그에 대한 기본 샘플 파일을 작성 합니다.

 

Worker

'use strict'

// 테스트 용 
let memoryArray = [];

let worker ={
  today_resource_filtering,
  filtered_resource_request_automation
}
function today_resource_filtering(){
  return new Promise(async(resolve,reject)=>{
    try {
      
      let i=0;
      let closeCondition = 1000;
      while(true && i < closeCondition){
        console.warn(`today_resource_filtering : ${i}`);
        i++;
        // await wait(1000);
      }
      console.warn('Test Complete');

      resolve(true);
    } catch (error) {
      reject(error);
    }
  })
}
function filtered_resource_request_automation(){
  return new Promise(async(resolve,reject)=>{
    try {
      console.warn('Request Server Resource');


      let i=10;
      let closeCondition = 1000;
      while(true && i < closeCondition){
        console.warn(`resource_automation_request : ${i}`);
        i++;
      }
      console.warn('Test Complete');
      resolve(true);
      
    } catch (error) {
      reject(error);
    }
  })
}
// 테스트용 유틸 

function wait(time){
  return new Promise(async(resolve)=>{
    setTimeout(()=>{
      resolve(true);
    },time)
  })
}

module.exports = worker;

 

 

Using Piscina

const path = require('path');
const Piscina = require('piscina');

const piscina = new Piscina({
  filename:path.resolve(__dirname,'filter_and_request.js')
});

(async function(){
  const res = await Promise.all([
    piscina.run({},{name:"today_resource_filtering"}),
    piscina.run({},{name:"filtered_resource_request_automation"}),
  ]);

  console.warn(res);
})();

Not Using Piscina

let worker = require('./filter_and_request.js');




(async function(){
  const res = await Promise.all([
    worker.filtered_resource_request_automation(),
    worker.today_resource_filtering()
  ]);

  console.warn(res);
})();

 

Using Piscina 결과부터 보겠습니다.

 

엥? 멀티스레드라고 되어있었는데 한 작업이 끝나야 다른작업이 시작됩니다. 

 

기존의 worker의 내용을 바꿔보겠습니다.

 

function today_resource_filtering(){
  return new Promise(async(resolve,reject)=>{
    try {
      let i=0;
      let closeCondition = 1000000000;
      while(true && i < closeCondition){
        i++;
        // await wait(1000);
      }
      console.warn('Test Complete');

      resolve(i+'==today_resource_filtering');
    } catch (error) {
      reject(error);
    }
  })
}

closeCondition 값을 바꾸면서 반복을 하고 

 

(async function(){
  console.time('using_basic_js');
  const res = await Promise.all([
    piscina.run({},{name:"today_resource_filtering"}),
    piscina.run({},{name:"filtered_resource_request_automation"}),
  ]);
  console.warn(res);
  console.timeEnd('using_basic_js');
})();

각각의 실행자에서 이렇게 테스트해보겠습니다.

 

100000부터 0을 하나씩 추가하면 (워커가 2개니까 *2 하시면 될 것 같습니다.)

 

1. 1000000  (20만)     ( Piscina - 73ms   vs Node.js (Single Thread )  3ms )

오잉? 왜 피시나에서 더 걸리는거같죠? 

2. 10000000  ( 2000 만 ) ( Piscina - 74ms   vs Node.js (Single Thread )  13ms )

천만 정도 루프시 특이점이 노드는 10 ms 증가 피시나는 7 정도 증가 했습니다.

3. 100000000 ( 20억 ) ( Piscina - 545 ms   vs Node.js (Single Thread )  950ms )

둘의 차이가점점 좁혀집니다.

 

4. 10000000000 (200억) ( Piscina - 9002 ms   vs Node.js (Single Thread )  17767ms )

100억번의 루프 -> 워커가 2개기 때문에 어찌보면 200억 인데

여기서부터 차이가 나기 시작합니다. ( Piscina의 진가가 발휘됩니다. )  

 

 

5. 마지막으로 (2000억 번 순환 ++시 ) ( Piscina - 1m 33s   vs Node.js (Single Thread )  3m 05ss )

점점 늘어날수록 차이가 벌어집니다. 2000억 순환시 3배의 가량이 차이가 발생하게 됩니다. 

-> 기존의 10만 100만정도는 piscina의 인스턴스를 생성하기 위한 기본 세팅값에서 들어가는 작업에 대한 시간으로 보입니다.

 

1차 정리 

Piscina는 기본 세팅 시 70ms 정도가 소요 된다.

Promise의 2개의 워커를 사용시 루프시간에 따라 다르지만  3배까지도 차이가 발생

 

 

바로 다음 포스팅에서는 최대워커 수만큼 호출시 와 지금 은 함수를 return new Promise로 해서 Promise.all 로 받았지만

사실 진짜 원하는건 멀티스레드아니였나 

A , B함수에서A가 계속 루프돌고 CPU 연산을 하면 B는 실행도 못하는게 실행되는게 아니였나? 라는 의문에 대한 해결을 위한 포스팅을 하겠습니다.

 

 

안녕하세요 깍돌이 입니다. 오랜만에 인사드리네요 기존에 작업했던 내용들 ( UI ) 혹은 그 뒤에 작업했던 내용들이

 

사실 거의 다 대외비성이라 이게 .. 참 포스팅을 할수 없다는게 아쉽네요 

 

이번에도 시작은 이렇게 하고 또 대외비로 빠질진 모르겠지만..  나름의 큰 프로젝트를 하고있기도 하고 이번 UI는 있는거 에 한해서 이래저래 조합조합 해서 진행할 예정입니다.

 

PlayWright 입니다. 핫하기도하고 팀내에서 먼저 쓰고 있는 분들이 있기도하고 일단 간단하게 맛만 봤었을때 특이점이 하나 있어서 선택해보게 되었습니다.

 

물론 저는 셀레니움 버전 1 , 버전 2(RC타입) , 퍼펫티어도 어느정도 해봤으니 이래저래 팀내 프로젝트도 진행하고있고 여러 이유를 이용해서 이걸 시작해도 되지만 같이 해야 하는 인원이 있는 만큼  여러 가지 고민중 선택하게 된 계기는 

러닝커브가 매우 낮다 입니다.

 

셀레니움에서 고민해야될 부분 들이 100가지가 있다면 실제로 playwright 에서는 없다고 봐도 무방합니다. (기본적인 트러블 슈팅이 아닌 근본적인 고민 부분에 대한 이야기입니다. ) 

 

대표적으로 Find Element 에 대한 모듈 분리 및 다른 전략들이 필요로 하는데 이중에 예외처리 해야되는 것들 중 자동화 하시는 분들이 어려워하는게 Element 를 찾지 못하는 경우 입니다.  해당 Text 값같은 걸 찾는 경우 

Element에서 inner Text인지 textContents인지 value인지 data-property 인지 매번 예외처리를 해주었어야 했지만 playwright 에서는 해당 부분을 전부 자체 개발된 API 를 통해서 지원해주기 때문에 러닝커브가 매우 낮다고 판단하였습니다.

 

https://playwright.dev/docs/intro

 

Installation | Playwright

Playwright Test was created specifically to accommodate the needs of end-to-end testing. Playwright supports all modern rendering engines including Chromium, WebKit, and Firefox. Test on Windows, Linux, and macOS, locally or on CI, headless or headed with

playwright.dev

 

기본적으로 설치 하고 튜토리얼을 해보면서 Puppeteer와 차이점? 같은 부분을 적고 정리 하는 과정을 적어 보겠습니다.

 

( 솔직히 PlayWright 검색하면 다 누구껄 복붙했는지 다 같은 내용의 블로그 밖에 없네요 ;; 인생 좀 쉽게 쉽게 가고 싶었지만 한땀 한땀 다시 찾아서 포스팅을 시작.. 추후에는 또 어떻게 될지 모르겠지만 ) 

 

설치나 이런것들은 여기저기 많이 있으니까 제외 튜토리얼 대로 

npm init playwright@latest 아래와같이 설치 

타입스크립트 선택

E2E 테스트 파일을 어디에 넣을건가 -> tests

Github Actions  - Yes

수동 실행 브라우저 설치 - Yes

 

 

설치 후 나오는 설명을 보면서 하나씩 다 해보게 되면

npx playwright test 

e2e 테스트를 실행한다고 되어있는데 이게 무슨 기준일까 해서 실행해보니

프로젝트 - tests 폴더에 example.spec.ts 를 실행합니다. 

-> 실행 경로는 playwright.config.ts에서 바꿀수 있지만 이건 나중에 다시 적겠습니다.

 

npx playwright test --ui

UI 모드로 실행하는 명령어입니다.

폴더에 tests - example.spec.ts를 기준으로 나타나게 되네요

 

클릭하는 시점에 어딜 누르는 지도 보여줍니다.

 

로그도 있고 잘만들어진거 같습니다. 당장은 쓸일이 있나 모르겠네요 하면서 경험적인 내용들을 포스팅하겠습니다.

 

npx playwright test --project=chromium

플레이라이트 내부에서는 여러 프로젝트들이 있는데 그 부분들 중에 선택해서 진행할수 있게 하는 명령어입니다.

TS다보니 해당 리스트가 type으로 표현되어있는데요 

 

수많은 장치들이 있고 맨 밑에 저희가 대부분 써야 할 

데스크탑 앱이 있네요 저게 다 되는지 어떻게 차이가있는지 그냥 해상도로만 구분한건지는 좀더 확인해봐야 할거 같습니다.

 

npx playwright test example

파일을 선택하여 실행한다고 되어있는데 

도대체 example을 뭘로보고 실행하는건지 이해가 되지않았습니다.  파일명의 spec.ts를 무시하고 실행한다 해도

changeexample로 제가 바꿔놨기 때문입니다. 그래서 몇번의 테스트를 해보았는데

 

일단 npx playwright test 명령어를 치게되면 config의 testDir을 기준으로 바라 보게 됩니다.

그래서 1차적으로 해당 폴더의 위치를 하게 되고 파일명을 변경해가면서 테스트를 해보니 

 

demo-todo-app.spec.ts에서

 

demo 도 되고 todo 되고 app 되고 - 도 됩니다. 결론은 like 검색으로 포함되어있다면 해당 파일을 실행하는거같습니다.

둘다 demo를 넣고 실행하게되면  아래의 사진은 demo가 먼저 걸리는 파일을 실행하는거같은데 이 순서를 바꿔보겠습니다.

순서를 바꿔서 1_demo 로 앞에 오는 것을 기대하고 테스트를 해보았으나 기존과같이 밑에 demo-todo를 실행하게 됩니다.

1_demo ( 케이스 2개 )

demo-todo ( 24개 ) 

조금 이상함을 느껴서 npx playwright test로 전체 테스트를 돌렸으나

전체가 돌아가지 않음을 확인했습니다. 

그래서 다시 이상함을 느끼고 확인해보니

.spec 이 들어가야 같이 돌아가는걸로 보여 다시 재 테스트를 하였습니다.

 

다시 demo를 공통으로 넣으니

 

다 돌아가는거 보면 demo가 포함되는 모든 것들이 돌아가는걸로 보입니다.

 

마지막 테스트 ppap 로변경하고 실행

 

 

tc-seocnd.spec.ts로 변경하고 직접 실행

spec을 제외하고 직접 실행

 

결론 : spec이 들어가야 실행이 가능 

npx playwright test --debug

디버그로 실행하여 한땀한땀 실행하면서 내용을 확인할수 있게 됩니다.

Resume을 누르면 그냥 통으로 실행하고 다음 스텝으로 넘어갑니다.  Step over 는 해당 테스트 안에서 한스텝씩 넘어 갈수 있습니다.

 

npx playwright codegen

말 그대로 코드젠입니다. 내가 하는 액션들을 코드로 간단하게 먼저 뽑아주는 역할을 합니다.

 

npx playwright test

 

 

여기까지가 기본 OVER VIEW였습니다.

 

포스팅 시간이 짧아서 OVER VIEW 2탄으로 돌아오겠습니다.

 

2탄 내용은

 

playwright.config 옵션에 대해

 

github action 연결 

 

내부 test모듈 구조 

 

왜 이 틀을 구조가 강제화되어있는가

 

worekr 구조 

개인적인 궁금증 해결 입니다.

 

 

 

 

 

안녕하세요 깍돌이 입니다.

 

옛날에는 학습하면서 있던 트러블 슈팅들을 전부 포스팅 했었는데 솔직히 변명이지만 현재 너무 바쁘게 일상생활을 살아가고있어서 ( 노는거 아님 ) 

 

오늘은 테라폼(IAC) 를 이용하여서 NCP ( Naver Cloud Platform ) 에 인프라를 세팅 할수 있도록 적용해보려고 합니다.

IAC에 대해서는 구글에 검색하면 엄청 설명 잘되어있는 곳이 있기 때문에 생략 하겠습니다. 

우선 IAC에 대해서는 대략적으로는 알고 있는 상태이기 때문에 테라폼에 대해서 알아 보겠습니다.

What is Terraform?

이제 앞으로 작성되는 개요에서 설명은 공식 홈페이지를 참고합니다. 

https://developer.hashicorp.com/terraform/intro

내용들을 읽다 보면 "클라우드 및 온프레미스 리소스를 안전하고 효율적으로 빌드, 변경 , 버전화 할수 있는 인프라 도구" 라고 되어있습니다.  대표적으로는 AWS , Azure, GCP, OCP , Docker 등을 설명하고 있습니다.

 

테라폼 구조

기본적으로 테라폼은 PROVIDER를 통해서 벤더 클라우드사의 API(OPEN API ) 와의 연동을 통해서 진행되는 구조임을 알수 있습니다.

크게 3가지의 구조를 가진다고 합니다.

Write : 리소스를 정의 (VPC , Subnet, VM 등의 배포 구성)

Plan : 기존 인프라 및 구성을 기반으로 생성, 업데이트 , 삭제 할 인프라를 어떻게 할지에 대한 계획을 설정

Apply : apply 할 경우 모든 리소스 종속성을 고려하여 올바른 순서대로 작업을 수행 ( vpc, subnet, vm 이 있다면 vpc-> subnet- vm 으로 올바른 순서를 찾아서 수행 ) vpc를 수정하게될 경우 확장전의 vpc를 재생성 

 

위와같은 순서를 보게 될 경우 크게는

 

API -> Write -> Plan -> Apply 같은 순서로 진행됨을 알수 있습니다. 

API : NCP의 ACC,SEC 설정

Write : .tf 소스를 작성하는 구간으로 어떤식으로 자원을 구성할지에 대한 설정

Plan : Write 에 작성된 tf를 읽어서  생성이 되는지 오류가 없는지 어떻게 생성되는 등에 대한 내용 을 확인

Apply : 현재 작성된 내용으로 인프라를 적용

 

Terraform

설명은 대충 여기까지만 하고 Windows 에서 설치 및 동작을 확인해보겠습니다.

https://developer.hashicorp.com/terraform/downloads

스펙에 맞는 테라폼 다운로드 및 압축 해제 ( zip으로 되어있고 압축 해제하면 terraform.exe만 나옵니다 )

C:\terraform 폴더 생성 후 이동

 

Windows - 고급 시스템 설정 - 고급 - 환경 변수 ( N ) 에서 

path 에 편집으로 테라폼 경로 추가  및 terraform --version 테스트

1.3.7 Windows_386 확인

기본적인 샘플 파일 작성 및 동작 확인을 위해서 main.tf , outputs.tf , variables.tf, versions.tf 를 작성합니다.

main.tf

provider "ncloud" {
  access_key = var.access_key
  secret_key = var.secret_key
  region     = var.region
}

data "ncloud_regions" "regions" {
}

data "ncloud_server_images" "server_images" {
}

resource "ncloud_server" "server" {
  name                      = var.server_name
  server_image_product_code = var.server_image_product_code
  server_product_code       = var.server_product_code
}

 

variable.tf

variable "access_key" { 
  default = ""
}

variable "secret_key" { 
  default = ""
}

variable "region" {
  default = "KR"
}

variable "server_name" {
  default = "terraform-test"
}

variable "server_image_product_code" {
  default = "SPSW0LINUX000046"
}

variable "server_product_code" {
  default = "SPSVRHICPUSSD002" 
}

versions.tf

terraform {
  required_version = ">= 0.13"
  required_providers {
    ncloud = {
      source = "navercloudplatform/ncloud"
    }
  }
}

terraform plan 후 terraform apply 하면 서버를 생성합니다. 

그리고 server_name 을 변경시

위와같이 plan 이 나오고  terraform apply 시 변경이 시작됩니다.

 

** 주의사항 기본 샘플 파일로 이것저것 하다가 문제점은 아니지만 기대결과랑 다른 경우가 있습니다.

NCP에서 서버는 server_image_product_code ( OS 코드 ) 와 server_product_code ( OS에 따른 스펙코드 ) 쌍으로 이루어지게 됩니다.

 

테라폼에서 OS코드에 맞지 않는 스펙코드를 사용할경우 500이 발생하며 중지되는건 이해가 됩니다. 

예를들면 Windows는 100G만 지원하지만 windows 2016에 리눅스 스펙코드를 넣으면 (리눅스는 only 50g) 오류가 나는게 정상이니까요 

 

하지만 지원되는 스펙 코드지만 오류가 나는 케이스가 있습니다.

NCP의 경우 Classic과 VPC 2개의 플랫폼을 제공하고 있는데요 

server spec change에서 다른점이 하나 있습니다. VPC는 Standard -> HiCPU 로의 타입을 넘어선 스펙변경이 가능하다는 점이고 Classic은 불가능 하다는 점입니다.

Classic에서 변경이 되는 타입은 Compact, Standard 입니다. 그외에는 모두 같은 타입에서만 스펙 변경이 가능합니다.

 

VPC같은 경우는 모든 스펙 을 넘어선 변경이 지원되기 때문에 문제가 되지 않습니다. 

 

그래서 저는 Terraform 은 해당 인프라를 구성해주는 역할을 한다고 생각 했기에 hicpu 로 만들어진 Classic서버를 Standard로 하고 apply 하면 오류가 없을거라 생각 했습니다.

 

HICPU -> Standard 로 변경시

오류 발생 

Classic에서는 지원되지 않는게 정상이기 때문에 API 를 통해 벤더사와 연결하는 테라폼에서는 위와같은 에러를 정상적으로 받고 apply 를 종료하게 됩니다.  아무생각없이 이경우라면 저는 다시한번 재시도해서 정지 -> 반납 -> 원하는 스펙으로 생성 해주길 기대했는데 그렇진 않았습니다.

 

서버 이름 변경시에는 terminate -> create 지만

서버 스펙 변경 은   stop -> modify -> boot (또는 modify)같은 형태로 넘어가게 됩니다.  테라폼에서 말하는 멱등성에서도 벤더사의 케이스에 따라 오류가 나는 부분이 있을수 있기 때문에 학습 하는 과정에서 이런 예외처리등을 많이 확인해야 될거 같습니다.

 

* 추가 - 서버 설명 변경도 벤더사에서 지원해주는 API 가 없기 때문에 stop - terminate -> create(이때 설명을 새로 넣음) 

같은 형태로 넘어가게 됩니다. 별거 아닌거 수정한다고 건드리면 자주 지우고 만들거 같네요

 

다음 포스팅은 data, resource 및 파일 구조 terraform plan, terraform apply 시 어떤 동작이 되는지에 대해 작성하겠습니다.

 

 

 

 

안녕하세요 깍돌이입니다.

 

자동화 시스템 운영중 Network Traffic Automation System 에 관련된 내용을 하나 적으려고 합니다.

 

시스템 구조상 명령을 내리는 Command Server 와 명령을 받아 행동을 하는 (자동화 케이스의 기능 ) Agent Server 가 있습니다.

 

Agent는 ONE Source 로 되어있습니다.  기존에는 VM 1대 = Agent 1대 였습니다. 하지만 이렇게 사용하게되면 네트워크가 복잡함에 따라서 TC를 늘릴때 마다 VM이 늘어나는 일이 발생하였으며 이로 인해서 효율화를 요청 받았습니다.

 

그로 인해서 케이스를 복잡하게 만들게 되었고 기존에는 1개의 포트 ( 6500Port ) 를 사용하였다면 지금은 VM 1개에서 6500 7500 8500 ~ .. .등으로 사용하게 되어 훨씬 더 적은 VM 으로 많은 케이스를 사용하게 되었습니다.

 

해당 포트로 운영하다 발생한 이슈가 하나 있어 공유 드리려고 합니다. 

 

spawn tcpdump ENOENT

UDP의 트래픽을 캡처 및 검증 하기 위해서 Node.js에서 Child_process의 spawn 을 사용하고 있으며

해당 spawn 에서 tcpdump (OS 레벨) 에서 직접 사용하고 있습니다.

그리고 기존에 VM 1개에서 실행되던 Agent는 nohup형태로 실행되어야 하기 때문에 pm2를 사용하였는데요 ( private 한 환경이라 local pm2를 사용합니다. ) 

package.json 

자동화 테스트 실행시 외부에서 UDP를 발생하게 되는데 위의 spawn 을 통해서 아래와 같이 정상적으로 받아오고 UDP 패킷을 확인할수 있습니다.

하지만 6500 7500 8500 ... x500으로 포트를 나눠서 에이전트를 관리하게 될시

재부팅시 pm2의 오류로 인해서 spawn 시 ENOENT 오류가 발생합니다.

Agent가 있는 VM의 경우 해당 Agent가 죽지 않도록 crontab이 걸려있습니다.

retry.sh 의 경우 pm2 list했을시 6500 7500 8500 등의 서비스가 없을 경우 재시작 해주는 역할을 하는데 서버 재시작 혹은 재부팅 혹은 내서버이미지로 서버 생성하여 새로 생성시 등의 경우 ENOENT 이슈가 발생합니다.

 

일단 하나씩 테스트 해봅니다.  ( 모든 전제조건은 npm run delete로 stop and delete를 했다는 조건 입니다. ) 

 

1. 현재 root계정에서 npm run restart 

실패

2. npm run delete & npm run start 

성공  ( npm run delete 시 pm2 save  to synchronize 워닝 발생 ) 

3. crontab 에서 확인

npm run delete상태기 때문에 npm run start & npm run restart가 됩니다.

실패

4. crontab 확인 ( retry.sh 코드 수정 )

 

재 실행 확인

crotnab으로 동작이 되어야 하지만 동작이 되지 않습니다. 스택 오버 플로우를 찾아보다 보면

spawn 에서 shell:true로 넣으라는 이야기가있는데 그렇게 해볼 경우 exit code 127 이 발생하면서 권한 문제로 종료 되게 됩니다. ( 사실 ENOENT 오류 자체가  " 일부 디렉터리는 권한 문제로 인해 또는 존재하지 않기 때문에 액세스할 수 없습니다" 라는 뜻입니다. ) 

 

ENOENT보단 나은 에러 메시지 아닌가?  권한 문제로 생각하고 pm2 save를 이용해보려고 합니다.

 

5. retry.sh 코드 수정 

npm run save ( pm2 save --force를 추가 )

npm run start

npm run restart

실패 

 

pm2를 커맨드 라인에서 직접 실행할경우만 정상적으로 권한 문제가 발생하지않고 실행이 되는 현상이 발생합니다.

 

그럼 crontab 에서 실행이 문제라고 판단되고 crontab에서 테스트 한 내용입니다.

 

1. sudo crontab -e

루트 권한으로 실행하기 위해서 sudo 를 사용합니다. -> 실패 

 

직접 커맨드라인에서 실행한게 아니라면 restart를 커맨드라인에서 직접 해도 오류가 발생합니다.

 

 

결론

npm run save 부분을 sudo 로 실행해야 권한 이 꼬이지 않고 save가 됩니다. 

npm run save는 

 

pm2 save --force 입니다. ㅎㅎ

pm2에 구조에 대해서 이야기 를 할까 했지만 해결한 것 으로 만족 하겠습니다...

 

지금은 Docker 기반이 아니라 위와 같은 이슈가 발생하고 ( 서버의 지속적 운영을 위해서 ) 

Docker 로 애초에 쓰시고 계신 분들은 아마 이슈가 없을 거라고 생각 합니다. 

Docker에서는 Docker의 이슈가 있겠지.. 내년엔 다 Docker 로 바꿉니다. 

 

감사합니다.

 

 

+ Recent posts