• 며칠 전 5월 5일, 유명 PaaS 인 Heroku 가 Docker 로 배포하는 도구를 발표했다. [1]
  • 요즘 회사에서, 사내 개발자들을 위한 PaaS (Heroku 또는 AWS Beanstalk 와 비슷) 를 만들고 있고, Docker 에도 관심이 많기 때문에 한 번 살펴봤다.
    • (내가 안 하면 아무도 안 해줄것 같기도 하고)
  • 살펴본 결론은:
    • 아무 Docker 이미지나 Heroku 에 올려서 돌릴 수 있는건 아니다.
      • Heroku 가 자동으로 만들어 주는 Dockerfile 을 사용하거나.
      • FROM heroku/cedar:14 이미지를 사용해야 Heroku에 올릴 수 있다. (/app 아래에 파일들이 들어가는 조건도 있고)
    • ONBUILD 로 코드를 이미지에 포함시키기 때문에, 소스코드를 수정하면 heroku docker:start 를 해줘야 한다.
      • 개발용으로 volume mount 를 하는 등의 다른 방법이 분명 있겠지?
    • 장점은 (내 생각에는)
      • 앱에 있는 바이너리 의존성 (e.g. imagemagick 같은것?) 을 buildpack 방식에서는 복잡하게 해결해야 했는데
      • Docker 방식에서는 단순히 Dockerfile 에 의존성을 추가하고 PATH 를 추가하면 된다.

heroku-docker 설치

$ heroku plugins:install heroku-docker

샘플 앱 clone

$ git clone https://github.com/heroku/node-js-getting-started.git
$ cd node-js-getting-started

docker:init - 현재 디렉터리의 언어에 따라 Dockerfile 생성

$ heroku docker:init
  Wrote Dockerfile (node)

$ cat Dockerfile
  FROM heroku/cedar:14

  RUN useradd -d /app -m app
  USER app
  WORKDIR /app

  ENV HOME /app
  ENV NODE_ENGINE 0.12.2
  ENV PORT 3000

  RUN mkdir -p /app/heroku/node
  RUN curl -s https://s3pository.heroku.com/node/v$NODE_ENGINE/node-v$NODE_ENGINE-linux-x64.tar.gz | tar --strip-components=1 -xz -C /app/heroku/node
  ENV PATH /app/heroku/node/bin:$PATH

  RUN mkdir -p /app/.profile.d
  RUN echo "export PATH=\"/app/heroku/node/bin:/app/bin:/app/node_modules/.bin:\$PATH\"" > /app/.profile.d/nodejs.sh
  RUN echo "cd /app/src" >> /app/.profile.d/nodejs.sh

  EXPOSE 3000

  ONBUILD RUN mkdir -p /app/src
  ONBUILD COPY . /app/src
  ONBUILD WORKDIR /app/src
  ONBUILD RUN npm install

docker:init 내부 구현

  • buildpack 과 동일한 방식으로 언어를 detect 한다.
    • e.g. package.json 가 존재하면 Node.js 로 detect 함. [2]
  • platforms.detect(dir); [3]
  • fs.writeFileSync(dockerfile, contents); [4]
  • 현재 지원되는 언어는 node, python, ruby, scala [5]
  • heroku docker:init --template minimal 으로 최소한의 Dockerfile 을 만들 수 있음.
    • 여기서 생성되는 Dockerfile 이 [6] 이것.

heroku/cedar:14 도커 이미지 내부 구현

  • 이미지는 Docker Registry 에 올라가 있음. [7]
  • Cedar-14 는 ubuntu 14.04 LTS 를 기반으로 한 스택이고, 설치되는 ubuntu package 는 여기에. [8]

Dockerfile 은 아래와 같이 단순함. [9]

FROM ubuntu-debootstrap:14.04
COPY ./bin/cedar-14.sh /tmp/build.sh
RUN LC_ALL=C DEBIAN_FRONTEND=noninteractive /tmp/build.sh \
  && rm -rf /var/lib/apt/lists/*
  • ubuntu-debootstrap 의 Docker Registry [10]
    • github.com/tianon/docker-brew-ubuntu-debootstrap 여기에 Dockerfile 이 있다는 것을 알 수 있음. [11]
    • ubuntu-debootstrap:14.04 의 Dockerfile [12]
  • heroku/cedar:14 는 용량이 작은 최소한의 ubuntu docker image 에
  • cedar-14.sh 를 실행시킨다. cedar-14.sh 의 코드 [13]

docker:start - 로컬에 컨테이너를 시작

$ heroku docker:start
  building image...
  Sending build context to Docker daemon 285.2 kB
  Sending build context to Docker daemon
  Step 0 : FROM heroku/cedar:14
  Pulling repository heroku/cedar
  66766a8c5395: Download complete
  c40496787f7c: Download complete
  f90b0e99841d: Download complete
  ac0bf528748f: Download complete
  Status: Downloaded newer image for heroku/cedar:14
   ---> 66766a8c5395
  ... docker 이미지 빌드 생략 ...
  Successfully built d3e547511a82

  starting container...
  web process will be available at http://localhost:3000/
  Node app is running on port 3000

docker:start 내부 구현.

  • start.js [14]
    • Procfile 을 읽어서 여기에 적혀있는 command 가 Docker 가 실행될때 command 로 전달됨.
    • docker.ensureStartImage [15]
      • docker.js#ensureStartImage() [16]
      • docker.js#buildImage [17]
        • build --force-rm --file="${dockerfile}" --tag="${id}" "${dir}"
  • docker.js#runImage() [18]
    • run -w /app/src -p 3000:3000 --rm -it ${mountComponent} ${envArgComponent} ${imageId} sh -c "${command}" || true [19]

컨테이너에 명령 실행

$ heroku docker:exec npm install --save --no-bin-links cool-ascii-faces
$ heroku docker:exec ls node_modules

docker:exec 내부 구현

  • docker exec 를 한 번 감싼 형태.
  • exec.js [20]
  • docker.js#runImage() [21]

docker:release - 배포

아래의 명령으로 배포 가능하다.

$ heroku docker:release
  • 이것은 heroku 가 기존에 사용하던 Buildpack 기반의 빌드시스템을 사용하지 않음.
  • 대신, Docker 이미지에 앱의 코드와 의존성을 포함하고, 로컬의 Docker 이미지를 직접 배포함.

docker:release 내부 구현

  • Heroku 로 배포.
  • 컨테이너를 올려서 /app 디렉터리를 압축한다.
    • 이 압축파일에는 language 의 runtime 과 앱의 소스코드를 포함함.

release.js [22]

docker run -d ${imageId} tar cfvz /tmp/slug.tgz -C / --exclude=.git --exclude=.heroku ./app
docker wait ${containerId}
docker cp ${containerId}:/tmp/slug.tgz ${slugPath}
docker rm -f ${containerId}
slug.tgz
  • 만들어진 slug 압축파일을 HTTP PUT 을 통해 업로드하도록 구현 되어 있음. [23] [24]