Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.

HashiConf 17 참석기 : Day 1

지난 19~20일에 미국 텍사스 오스틴에서 열린 HashiConf에 참가하고 왔다. 매년 해외 콘퍼런스를 하나 정도는 참여하려고 하는 편인데 올해는 어느 콘퍼런스를 갈지 고민을 많이 했다. 작년에 Node Interactive North America를 참석하면서 텍사스 오스틴에 갔다 왔는데 나한테는 도시가 좀 재미가 없었고 가기도 불편해서 별로 가고 싶지 않았다. 그래서 dockercon도 포기했었는데 결국은 오스틴에 다시 갔다 왔다.

HashiConf와 Node Interactive를 마지막까지 고민하다가 HashiConf를 선택했다. 도시는 Node Interactive가 캐나다 밴쿠버에서 해서 더 맘에 들었지만, 작년에는 샌프란시스코를 들르지 못해서 올해는 가고 싶었기에 샌프란시스코를 낀 일정으로 잡으려다 보니 캐나다를 들르는 일정은 비용이 너무 많이 들었다.

비용이 가장 큰 이유이고 콘퍼런스를 많이 가보다 보니 너무 큰 콘퍼런스보다는 좀 규모가 작아서 커뮤니티 느낌 나는 콘퍼런스가 나한테는 훨씬 좋게 느껴졌다. HashiConf도 그 규모는 좀 넘어선 것 같지만(올해는 800명 규모) 그래도 Node 콘퍼런스는 가본 적이 있고 최근에 HashiCorp의 도구를 많이 쓰면서 관심이 많았기에 HashiConf를 선택했다.

HashiCorp

HashiCorp는 클라우드에서 개발 - 배포까지 관련된 도구를 주로 만드는데 개인적으로는 회사가 국내에서 많이 알려지진 않은 느낌이지만 내가 무척 좋아하는 회사 중 하나이다.


재작년에 사무실에 방문해서 인상이 좋았는데 HashiCorp의 제품을 사용하면서 너무 잘 만들어서 반해버렸다.

HashiCorp 제품군

국내에서는 Vagrant가 제일 유명하고 Consul이 그다음으로 유명하지 않나 싶다. 보통 HashiCorp라고 하면 잘 모르고 Vagrant 만든 회사라고 하면 대부분 알아듣는 것 같았다. HashiCorp에 대해 더 알고 싶다면 HashiCorp의 도 (The Tao of HashiCorp)를 읽어보길 권한다.

Day 0

콘퍼런스는 18일부터 20일까지였지만 18일은 Training이 있는 날이었다. 콘퍼런스 바로 전날 도착하면 시차 적응 때문에 세션 듣다가 피곤해서 나는 17일에 오스틴에 도착했다. Training은 799달러나 별도로 내야 했기에 잠시 고민하다가 듣지 않고 쉬었다.

IMG_8879


오스틴 시내를 왔다갔다 하면서 놀다가 점심때부터 등록을 받는다길래 JW Marriott 호텔에 들렸다. 작년 Node Interactive가 한 곳과 같았으므로 찾는데는 어렵지 않았다.

IMG_8877


에코백에 티셔츠랑 명찰을 포함한 기념품을 받았다. 좋아하는 회사라 그런지 회사 로고가 곧곧에 있는 것만으로도 왠지 설렜다.

IMG_8894


저녁에는 Welcome Reception이 행사장에서 있었다. 메일로 Welcome Reception이 있다고 안내가 왔는데 뭐하는 행사인지 몰라서 가서 물어봤더니 그냥 친목 행사라고 했다. 각 부스를 배치해놓은 Hub라는 공간에서 술과 음식을 나눠주고 알아서 네트워킹하는 자리였다. 이런 곳에서 네트워킹은 영어가 안 되면 몹시 어려운 일이었기에 부스를 구경하면서 대충 놀면서 음식을 얻어먹었다. 부스는 HashiCorp의 부스가 제품별로 있었고 구글 클라우드 플랫폼, 디지털 오션 등 스폰서 사의 부스가 있었다.

Day 1


Keynote

IMG_8898


시작은 2시간 정도의 키노트로 시작했다. 키노트는 HashiCorp의 공동 창업자인 Mitchell Hashimoto와 Armon Dadgar 등이 나와서 HashiConf와 회사를 소개하면서 HashiCorp의 각 제품을 하나씩 소개했다. 각 제품을 소개하면서 해당 제품을 활용하는 다른 회사의 발표자가 나와서 설명을 같이하는 식으로 진행했는데 제품마다 새로운 것들을 하나씩 발표했기에 마치 Apple의 WWDC나 Google I/O를 보는 것 같은 기분이 들었다. 나는 콘퍼런스 중에서 이 키노트가 제일 인상적이었는데 기술회사가 이런 식으로 제품을 소개할 수가 있구나 싶어서 감동하였다.

키노트에서 발표된 것으로는 다음의 내용이 있다.

더 자세한 내용은 HashiCorp의 블로그에서 볼 수 있다. 특히 Sentinel을 발표할 때는 뭔가 새로운 걸 발표할 분위기로 설명해서 엄청 두근두근했고 Policy as code라며 Sentinel을 발표했을 때 요즘 고민하던 부분을 딱 맞게 긁어주는 것 같아서 뒤통수를 맞은 기분이었다. 다만 Sentinel은 엔터프라이즈 기능이라서 써보지는 못할 것 같다. ㅠ

IMG_8908


HashiConf는 2015년에 처음 300명 규모로 열어서 작년에 500명 규모이고 이번에는 800명 규모라고 한다. HashiCorp의 직원도 많이 늘어서 지금은 130명 정도이고 전 세계에 42개의 HUG가 있고 7,000명 정도의 멤버가 있다고 한다.

How to Build Reusable, Composable, Battle-tested Terraform Module - Yevgeniy Brikman, Gruntwork

Terraform의 모듈을 설명하는 세션으로 module이 인프라를 더 잘 배포하고 관리하는 방법이라고 얘기하고 있다. 이 발표자는 Terraform: Up & Running의 저자이고 DevOps as a Service 회사인 Gruntwork의 공동 창업자이다.

IaaS와 PaaS를 비교했을 때 PaaS는 사용하기 쉽지만 커스터마이징, 디버깅, 스케일링 등에 제약이 있다. Terraform의 모듈을 사용하면 IaaS를 PaaS처럼 사용하기 쉽게 추사화해서 사용할 수 있다. 코드에서 함수의 핵심 아이디어는 재사용, 테스트, 조합, 추상화인데 테라폼의 모듈을 이와 똑같이 생각할 수 있다. 모듈에 인풋을 주고 결과를 출력할 수 있다. 경험상 modules 폴더에 각 서브 모듈을 작성하고 examples 폴더에 사용방법을 예시로 넣고 test 폴더에 자동화된 테스트를 작성해 넣는 게 좋았다. 테스트는 보통 통합테스트를 작성하는데 발표에서는 go로 Vault를 올려서 테스트하는 코드를 보여주었다.

모듈은 상대경로로 지정할 수 있지만 테라폼 저장소나 Git 주소도 가능하다. Git을 사용할 때 git의 태그를 사용할 수 있으므로 특정 버전을 지정할 수 있다. 인프라를 업그레이드할 때 이 모듈의 버전을 올려서 업그레이드한다.

현재 팀에서 Terraform을 사용하면서 모듈을 안 쓰고 있는데 이 발표에서 모듈을 어떻게 써야 할지 감이 오는 느낌이었다.

Your Secret's Safe with Me - Securing Container Secrets with Vault - Liz Rice, Aqua Security

시크릿을 어떻게 다루어야 하는지를 설명한 세션이었다. 시스릿은 다른 사람이 보면 안 되는 정보로 암호화, 접근관리, 생명주기가 중요한데 이런 정보를 소스코드나 도커파일 등에 두면 안 된다고 했다. 환경변수에도 넣으면 안 된다고 했는데 환경변수로 지정해서 띄운 Docker에 들어가거나 docker inspect, /proc 명령어로 Docker 컨테이너에서 시크릿 정보를 확인하는 데모를 보여주었다.

결국, 발표는 Aqua에서 연동해서 제공하는 기능을 쓰라는 쪽으로 이어졌는데 Aqua와 연동해서 사용할 때 위의 방법으로도 시크릿 정보가 확인되지 않는 것을 보여주었다. 나는 솔루션 외에 Vault로 시크릿을 제대로 관리하는 방법이나 경험을 더 기대했기에 내용은 그냥 그 정도였다.

Terraform Abstractions for Safety and Power - Calvin French-Owen, Segment

Segment에서는 Terraform을 0.4버전부터 2.5년 정도 사용했다. 현재 30명 이상의 개발자가 매주 테라폼으로 작업하고 하루에 30~50번 정도 apply를 하고 있다. 개발자가 소프트웨어를 선택하는 조건을 조사해보니 개발자는 자신이 모르는 영향이나 위험이 있으면 잘 선택 안 한다는 조사 결과가 있다. 그러므로 안전하다는 의미는 개발자가 도구를 도입한다는 의미이다.

IMG_8930


테라폼이 무섭게 느껴질 수도 있지만 그럴 필요는 없다. 테라폼은 state를 갖는데 이로썬 안정성을 얻을 수 있다. 그래서 state를 관리할 때는 항상 remote state를 사용해야 한다. Segment에서는 테라폼의 핵심 부분은 코어 팀에서 관리하고 있다.

모듈도 많이 쓰는데 modules 폴더에 모듈을 모아놓고 재사용해서 사용하고 있다. README.md에 input 값을 정리해서 적어놓고 terraform-docs로 이를 문서로 생성하고 있다. output.tf는 JSON으로 렌더링해서 사용하고 있다. Segment에서 사용하는 모듈을 오픈소스로 공개해 놓았는데 참고용으로 좋아 보인다. 실제로 사용하는 경험을 잘 풀어주어 아주 좋은 발표였다.

Evening Social

첫날 저녁에는 파티가 있었다. 해외 콘퍼런스는 항상 이런 행사가 있는 듯... JW Marriott에서 20분 정도 있는 공간에 있었는데 어떤 술집을 통째로 빌려서 라이브 공연하면서 소시지 등을 구워서 나눠주고 드래프트 맥주를 계속 제공해 주었다.

IMG_8943


이렇게 음악이 나오는 곳에서는 특히나 영어를 알아듣기가 너무 힘들었다. 가뜩이나 잘 안 들리는데 음악 소리가 너무 커서 공연하지 말라고 하고 싶을 정도였다. ㅠ

IMG_8946


혼자서 놀고 있는데 콘퍼런스 장에서는 몰랐다가 한국분으로 보이는 분들이 몇 분 보였다. 한 분이 와서 인사를 해서 알게 되었는데 DevSisters에서도 3분이 오셨고 보스턴 Cisco에서도 오신 한국분이 있으셔서 얘기를 많이 나누었다. 한국 돌아가서도 HashiCorp 도구를 주제로 세미나를 할 수 있을 것 같았다. 국내에서 HashiCorp 도구를 많이 쓰는 회사를 몰라서 궁금했는데 이번 기회에 알게 되어 논의하고 공유할 수 있을 것 같다.



이 글은 HashiConf 17 참석기 : Day 2로 이어진다.

2017/09/30 23:32 2017/09/30 23:32

Puppeteer를 AWS Lambda에서 실행하기

Puppeteer를 소개했는데 내가 Headless Chrome을 사용하면서 계속하던 작업은 Headless Chrome을 AWS Lambda에 올리는 것이다. Headless Chrome으로 AWS Lambda에서 웹사이트 스크린샷 찍기에서 설명했듯이 Lambda에서 사용할 수 있으면 다른 대부분 환경에서 사용할 수 있고 운영 걱정 없이 사용할 수 있다.

Puppeteer가 사용하기 편하다는 걸 알았으니 이를 이용해서 Lambda에 올려봤다. 이미 Lambda에도 Headless Chrome이 동작하는 것은 확인했으니 이론상은 문제없지만, Puppeteer가 Chromium을 내장하고 있어서 확인이 필요했다.

Puppeteer 코드

Lambda로 테스트하기 위해 간단한 예제를 작성해 보자.

const puppeteer = require('puppeteer');

const getTitle = () => {
  return puppeteer.launch()
    .then((browser) => {
      return browser.newPage()
        .then((page) => {
          return page.goto('https://blog.outsider.ne.kr/', { waitUnitl: 'networkidle' })
            .then(() => page.evaluate(() => {
               return document.querySelector('title').innerHTML;
            }))
        })
        .then((title) => {
          browser.close();
          return title;
        });
    });
};

exports.handler = (event, context, callback) => {
  getTitle()
    .then(context.succeed)
    .catch(context.fail);
};

위 코드는 Puppeteer로 이 블로그에 접속해서 <title>의 내용을 반환하는 간단한 코드이다. 이 코드를 이용해서 Lambda를 올려보자.

headless_shell 사용

Puppeteer만 설치하고 이 파일 하나만 있어서 Zip으로 압축하면 68MB가 나오고(macOS 기준) puppeteer에 설치된 Chromium은 macOS 용이므로 Lambda에서 사용하려면 Linux용을 사용해야 한다.

지난 글에서 Chromium 프로젝트에서 headless_shell을 빌드해서 사용했는데 Puppeteer를 사용하기 위해 v62.0.3198.0용 headless_shell을 올려두었다. [Puppeteer 릴리스 페이지](https://github.com/GoogleChrome/puppeteer/releases에 가면 타겟 Chromium 버전을 볼 수 있는데 여기서는 Puppeteer v0.10.2를 사용한다.(v0.11.0용 headless_shell은 아직 빌드를 하지 못했다.) 이 headless_shll을 다운 받아서 실행 권한(+x)을 주어야 한다.

const getTitle = () => {
  return puppeteer.launch({
    executablePath: process.env.CHROME_PATH,
    dumpio: true,
    args: [
      '--no-sandbox',
      '--vmodule',
      '--single-process',
    ],
  })
    .then((browser) => {
      return browser.newPage()
        .then((page) => {
          return page.goto('https://blog.outsider.ne.kr/', { waitUnitl: 'networkidle' })
            .then(() => page.evaluate(() => {
               return document.querySelector('title').innerHTML;
            }))
        })
        .then((title) => {
          browser.close();
          return title;
        });
    });
};

앞의 코드에서 getTitle함수를 다시 보면 puppeteer.launch()에 옵션을 추가했다. 가장 중요한 부분은 executablePath 옵션이고 여기서는 환경변수 CHROME_PATH로 다운받은 headless_shell을 바라보도록 /var/task/headless_shell로 지정할 것이다. dumpio: true 부분은 Chrome의 동작을 디버깅하려고 로깅을 추가한 것이고 args 부분은 리눅스에서 사용하려고 크롬의 플래그를 추가한 것이다. 크롬의 기본 플래그는args 옵션을 사용한다.

Puppeteer의 Node.js 6.x 지원

용량문제를 해결하기 위해서 이전 글에서 webpack을 사용한다고 했다. npm으로 설치한 모듈에서 필요한 코드만 포함되도록 해서 50MB 제한을 넘지 않으려면 꼭 필요한 설정이다.

처음에는 기존과 같게 webpack으로 dist.js를 빌드했는데 실행하려고 보니 dist.js 파일에 async/await 코드가 포함되면서 오류가 발생했다. Lambda는 현재 Node.js 6.10까지만 지원한다.

Puppeteer는 초기에 나올 때는 Node.js 7.6.0 이상으로 만들어졌으므로 async/await를 많이 사용하고 있었고 저장소의 예제도 모두 async/await로 되어 있다. 하지만 이후 Node.js v6에 대한 지원이 추가되었다.

Puppeteer의 index.js를 보면 다음과 같이 되어 있다.

let folder = 'lib';
try {
  new Function('async function test(){await 1}');
} catch (error) {
  folder = 'node6';
}

module.exports = require(`./${folder}/Puppeteer`);

이는 런타임에서 async 함수의 존재를 확인하고 타겟 폴더를 바꿔준다. 저장소에는 node6이라는 폴더가 없지만 빌드할 때 async/await를 Node.js v6에 맞게 트랜스파일하므로 설치한 버전에서는 이 폴더가 존재한다. 하지만 webpack을 코드를 실행하면서 dist.js를 만드는 것이 아니므로 이 동적인 require()를 제대로 처리 못 하고 두 가지 버전의 파일을 모두 가져오면서 문제가 되는 것이다.

// webpack.config.js
const path = require('path');

module.exports = {
  target: 'node',
  entry: './index.js',
  output: {
    path: path.resolve(__dirname),
    filename: 'dist.js',
    libraryTarget: 'commonjs2',
  },
  externals: {
    'aws-sdk': 'aws-sdk',
  },
  resolve: {
    alias: {
      puppeteer: 'puppeteer/node6/Puppeteer.js',
    },
  },
};

이는 webpack 설정에서 resolve로 사용 파일을 지정해주면 해결할 수 있다.

Lambda에서 실행

이제 다 만들었으므로 Lambda에 올려보자. webpack으로 빌드한 뒤 dist.jsheadless_shell 2개의 파일을 압축하면 44MB의 zip 파일을 만들 수 있다.

AWS Lambda에 zip을 올리는 화면

AWS Lambda에서 새 Lambda를 만들고 zip 파일을 올리고 환경변수를 설정했다.

Lambda가 정상적으로 실행된 화면

테스트 실행을 하면 Puppeteer로 내 블로그의 제목을 잘 가져온 것을 볼 수 있다.

2017/09/29 23:25 2017/09/29 23:25