블로그 이미지
평범하게 살고 싶은 월급쟁이 기술적인 토론 환영합니다.같이 이야기 하고 싶으시면 부담 말고 연락주세요:이메일-bwcho75골뱅이지메일 닷컴. 조대협


Archive»


 
 

쿠버네티스용 Continuous Deployment 툴인 Skaffold

조대협 (http://bcho.tistory.com)

What is skaffold?

쿠버네티스 기반에서 개발을 하고 테스트를 하려면 일반적으로 다음과 같은 절차를 거쳐야 한다.

  • 소스 코드를 수정한 후, 

  • 수정한 코드를 컴파일 한 다음에

  • 컴파일한 소스 코드를 포함해서 Dockerfile을 이용해서 컨테이너로 패키징 한후에

  • 컨테이너를 레파지토리 새로운 버전 태그를 붙여서 업로드 하고

  • 쿠버네티스의 기존 Deployment나 Pod의 yaml 파일에 image 명을 바꾼후

  • kubectl -f apply 를 이용해서 변경된 파일을 반영하고,

  • 다음 public IP가 있는 서비스의 경우에는 public IP로 접속하고, 아닌 경우에는 SSH 터널링을 이용해서 해당 Pod나 Service에 접근해서 테스트를 한다.

한번의 소스 코드 수정을 하고 나서, 이러한 일련의 과정을 거쳐야 하는데, 코드 수정 하나를 반영하기 위해서 해야 할일들이 많다.

이러한 문제를 해결하기 위해서 코드 수정이 쿠버네티스에 반영되기 까지의 과정을 자동화를 통해서 단순화 해주는 프레임워크가 skaffold 이다. 

아래 그림은 skaffold의 개념인 워크 플로우를 도식화한 그림이다. 


<그림. Skaffold의 워크플로우>

출처 https://skaffold.dev/docs/concepts/


Skaffold가 실행되면, Skaffold는 소스 코드 리포지토리(ex git) 또는 로컬 디렉토리를 모니터링 한다. 소스 코드의 변화가 생기면 자동으로 코드를 빌드하고, 이를 도커 컨테이너로 패키징 한다. 

다음 컨테이너화된 이미지에 태그를 붙이는데, 단순하게 SHA256 해쉬를 생성하거나 또는 날짜나 Git commit ID를 태그로 사용한다. Git commit ID를 태그로 사용할 경우에는 코드 변경 내용이 어느 컨테이너 버전에 반영되어 있는지 추적할 수 있는 추적성을 제공한다.

태깅이된 컨테이너 이미지를 지정되어 있는 컨테이너 리파지토리에 저장하고, 미리 정의되어 있는 쿠버네티스 YAML 파일을 이용하여 변경 내용을 대상 쿠버네티스 클러스터에 반영한다. 이 과정은 개발자가 코드 변경만 하게 되면 전 과정이 자동으로 진행되기 때문에, 개발 환경으로의 코드 반영에 대해서 수동 작업이 필요없고, Skaffold를 종료 시키게 되면, 개발을 위해서 생성된 쿠버네티스 리소스 (Service, Pod 등)을 자동으로 삭제해주기 때문에, 매우 쾌적한 개발환경에서 개발이 가능하다.


원래 컨셉 자체는 CD(Continuous Deployment)의 용도이지만, 로컬 환경에서 변경된 소스코드를 바로 쿠버네티스에 배포 해주기 때문에 특히나 개발 환경 구축에 편리한 장점을 가지고 있다.

Hello Skaffold

상세한 개념 이해에 앞서서 먼저 간단한 예제를 통해서 어떻게 동작하는지 살펴보도록 하자

준비

예제 환경에는 쿠버네티스 클러스터가 이미 하나가 있어야 하고, kubectl이 로컬(랩탑)에 설치되어 쿠버네티스 클러스터와 연결이 되어 있는 상태이어야 한다. 

설치

설치 방법은 공식 문서 https://skaffold.dev/docs/getting-started/ 를 참고하면된다.

MAC의 경우에는 아래와 같이 curl 명령어를 이용해서 바이너리를 다운로드 받은 후 사용하면 된다.


curl -Lo skaffold https://storage.googleapis.com/skaffold/releases/latest/skaffold-darwin-amd64

chmod +x skaffold

sudo mv skaffold /usr/local/bin


예제코드 다운로드

예제 코드는 GoogleContainerTools 깃허브에서 다운로드 받을 수 있다.


%git clone https://github.com/GoogleContainerTools/skaffold


예제코드는 examples 디렉토리에 있는데, 그 안에 node 폴더의 내용을 이용하도록 한다.

%cd examples/node


예제 디렉토리 안에는 다음과 같은 파일들이 있다.

  • backend/Dockerfile :

  • backend/package-lock.json , package.json

  • backend/src/ : node.js 소스 코드

  • k8s/ : 쿠버네티스 리소스 배포용 yaml 파일

  • skaffold.yaml : Skaffold 워크플로우를 정의한  yaml 파일


node.js 소스 코드

src/index.js 는 node.js 소스 코드로, node.js express 웹 프레임웍을 이용해서 “Hello World!” 문자열을 리턴하는 웹 서버를 3000 번 포트로 기동 시키는 코드이다.


const express = require('express')

const { echo } = require('./utils');

const app = express()

const port = 3000


app.get('/', (req, res) => res.send(echo('Hello World!')))


app.listen(port, () => console.log(`Example app listening on port ${port}!`))

소스 코드 이외에 필요한 패키지들을 package.json에 정의되어 있다.

Dockerfile

Dockerfile의 내용은 다음과 같다. 


FROM node:10.15.3-alpine


WORKDIR /app

EXPOSE 3000

CMD ["npm", "run", "dev"]


COPY package* ./

RUN npm install

COPY . .


알파인 리눅스 기반의 node.js 10.15-3 이미지를 베이스 이미지로 하고, /app 디렉토리에 package.json 과 기타 소스코드를 복사 한후에, npm install로 의존성 패키지를 설치하고, package.json에 지정된 nodemon src/index.js 명령을 이용해서 앞에서 작성한 index.js node.js 코드를 실행하도록 한다. 

그리고 EXPOSE 3000 명령어를 이용해서 도커 컨테이너에서 3000번 포트로 Listen을 하도록 한다.

쿠버네티스 설정 파일

그러면 앞에서 만들어진 컨테이너로 쿠버네티스에서 서빙을 하기 위해서 쿠버네티스 리소스를 정의해야 하는데, ./k8s/deployment.yaml에서  Service와, Deployment를 정의 하도록 하였다.


apiVersion: v1

kind: Service

metadata:

  name: node

spec:

  ports:

  - port: 3000

  type: LoadBalancer

  selector:

    app: node

---

apiVersion: apps/v1

kind: Deployment

metadata:

  name: node

spec:

  selector:

    matchLabels:

      app: node

  template:

    metadata:

      labels:

        app: node

    spec:

      containers:

      - name: node

        image: gcr.io/terrycho-personal/node-example

        ports:

        - containerPort: 3000


여기서 컨테이너 리포지토리의 경로는 자신이 사용하는 경로로 변경해야 한다. 이 예제에서는 구글 클라우드 리포지토리 (GCR)에 저장하도록  경로를 지정하였다.

Skaffold 설정 파일

다음으로 skaffold의 설정 파일을 보자.


apiVersion: skaffold/v1beta11

kind: Config

build:

  artifacts:

  - image: gcr.io/terrycho-personal/node-example

    context: backend

    sync:

      manual:

      # Sync all the javascript files that are in the src folder

      # with the container src folder

      - src: 'src/**/*.js'

        dest: .


컨테이너 이미지의 경로를 정해놓고, 소스 코드 파일을 어떻게 sync 할지를 지정한다. sync.manual 로 로컬 디렉토리의 src/**/*.js 파일이 변경이 되면 이를 반영하도록 지정하였다. 

실행

그러면 해당 파일을 실행해보도록 하자 skaffold dev 명령어를 실행하면 위의 파일들을 기반으로 컨테이너화를 하고, deployment.yaml 을 이용해서 쿠버네티스 리소스 (Service, Deployment)를 생성 한후, 컨테이너를 배포하여 서비스할 수 있는 형태로 준비한다. 

이때 --port-foward 옵션을 줄 수 있는데, 위의 예제의 경우에는 Service가 있고, Service 타입이 Load balancer이기 때문에, External IP를 가질 수 있어서, 쿠버네티스에서 배정된 External Service를 통해서 Pod에 접근해 서비스가 가능하지만, 일반적으로 개발을 할때는 Service 까지 배포할 필요가 없고 단순하게 Pod만 배포하고 싶은 경우가 있다. (단순하게 작업하기 위해서)

이때 --port-forward 옵션을 주면 Pod에서 외부로 오픈된 포트로 로컬 환경(랩탑) 포트 Forwarding을 해준다.

즉 이 예제에서는 로컬(랩탑)에서 localhost:3000번을 Pod의 3000번 포트로 포트 포워딩을 해주게 된다.  이 옵션을 추가해서 실행하게 되면 결과는 아래와 같이 된다. 


%skaffold dev --port-forward

Generating tags...

 - gcr.io/terrycho-personal/node-example -> gcr.io/terrycho-personal/node-example:v0.32.0-28-g6bd1d50a

Tags generated in 91.565177ms

Starting build...

Building [gcr.io/terrycho-personal/node-example]...

Sending build context to Docker daemon  101.4kB

Step 1/7 : FROM node:10.15.3-alpine

 ---> 56bc3a1ed035

Step 2/7 : WORKDIR /app

 ---> Running in d4579ab15f5a

Removing intermediate container d4579ab15f5a

 ---> 7c725818faae

Step 3/7 : EXPOSE 3000

 ---> Running in e05be7cb896b

Removing intermediate container e05be7cb896b

 ---> 49ac353388c6

Step 4/7 : CMD ["npm", "run", "dev"]

 ---> Running in 52b216a93e63

Removing intermediate container 52b216a93e63

 ---> 3b7878ac4c42

Step 5/7 : COPY package* ./

 ---> 457e460aae8d

Step 6/7 : RUN npm install

 ---> Running in ed391e236197


> nodemon@1.18.7 postinstall /app/node_modules/nodemon

> node bin/postinstall || exit 0


Love nodemon? You can now support the project via the open collective:

 > https://opencollective.com/nodemon/donate


npm WARN backend@1.0.0 No description

npm WARN backend@1.0.0 No repository field.

npm WARN backend@1.0.0 No license field.

npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@1.2.4 (node_modules/fsevents):

npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@1.2.4: wanted {"os":"darwin","arch":"any"} (current: {"os":"linux","arch":"x64"})


added 265 packages from 161 contributors and audited 2359 packages in 5.381s

found 1 high severity vulnerability

  run `npm audit fix` to fix them, or `npm audit` for details

Removing intermediate container ed391e236197

 ---> f7a7217f7a60

Step 7/7 : COPY . .

 ---> 5b713462d7ac

Successfully built 5b713462d7ac

Successfully tagged gcr.io/terrycho-personal/node-example:v0.32.0-28-g6bd1d50a

The push refers to repository [gcr.io/terrycho-personal/node-example]

5723b6d27633: Preparing

4f0634afaa8a: Preparing

51a09ffa90b0: Preparing

4789f3ba672b: Preparing

28bf756b6f8e: Preparing

4c299e1e70d5: Preparing

f1b5933fe4b5: Preparing

4c299e1e70d5: Waiting

f1b5933fe4b5: Waiting

28bf756b6f8e: Layer already exists

4c299e1e70d5: Layer already exists

f1b5933fe4b5: Layer already exists

51a09ffa90b0: Pushed

4789f3ba672b: Pushed

5723b6d27633: Pushed

4f0634afaa8a: Pushed

v0.32.0-28-g6bd1d50a: digest: sha256:bccf087827dd536481974eb28465ce4ce69cf13121589e4a36264ef2279e9d1d size: 1786

Build complete in 27.759843507s

Starting test...

Test complete in 17.963µs

Starting deploy...

kubectl client version: 1.11

kubectl version 1.12.0 or greater is recommended for use with Skaffold

service/node created

deployment.apps/node created

Deploy complete in 1.606308148s

Watching for changes every 1s...

Port Forwarding node-6545db86c5-wkdc4/node 3000 -> 3000

[node-6545db86c5-wkdc4 node] 

[node-6545db86c5-wkdc4 node] > backend@1.0.0 dev /app

[node-6545db86c5-wkdc4 node] > nodemon src/index.js

[node-6545db86c5-wkdc4 node] 

[node-6545db86c5-wkdc4 node] [nodemon] 1.18.7

[node-6545db86c5-wkdc4 node] [nodemon] to restart at any time, enter `rs`

[node-6545db86c5-wkdc4 node] [nodemon] watching: *.*

[node-6545db86c5-wkdc4 node] [nodemon] starting `node src/index.js`

[node-6545db86c5-wkdc4 node] Example app listening on port 3000!


태그를 생성해서 gcr.io/terrycho-personal/node-example:v0.32.0-28-g6bd1d50a 이라는 이름으로 컨테이너를 만들어서 쿠버네티스에 배포하고 Port Forwarding node-6545db86c5-wkdc4/node 3000 -> 3000 에서 보는 것과 같이 Pod  node-6545db86c5-wkdc4의 3000 번 포트로 로컬 포트 3000번을 포워딩 하였다. 


kubectl 명령어를 이용해서 Deployment, Pod 그리고 Service들이 제대로 배포 되었는지를 확인해보면 다음과 같다. 


%kubectl get deploy

NAME                    DESIRED CURRENT UP-TO-DATE   AVAILABLE AGE

node                    1 1 1         1 3m


%kubectl get svc

NAME                               TYPE CLUSTER-IP EXTERNAL-IP      PORT(S) AGE

node                               LoadBalancer 10.23.254.227 35.187.196.76    3000:30917/TCP 3m


%kubectl get pod

NAME                                     READY STATUS RESTARTS AGE

node-6545db86c5-wkdc4                    1/1 Running 0 17m


모든 자원들이 제대로 올라온것을 확인했으면 해당 Pod에 localhost:3000 터널을 이용해서 접속해 보자. 



이 상태에서 src/index.js 내용을 에디터를 이용해서 변경해서 저장하면, skaffold 가 파일이 변경되었다는 것을 인지하고, 도커 컨테이너 빌드와 재 배포를 자동으로 수행한다. 아래는 index.js에서 “Hello World 2!”를 출력하도록 소스코드를 수정하여 저장한 후, 수정 내용이 반영된 결과이다. 


중지

개발을 끝내고 싶을 때는 실행중인 skaffold만 중지하면, 아래와 같이 개발을 위해서 생성했던 이미지와 쿠버네티스 리소스 (Deployment, Service, Pod)등을 클린업 해주기 때문에 깔끔하게 개발했던 환경을 정리할 수 있다. 


^CPruning images...

untagged image gcr.io/terrycho-personal/node-example@sha256:bccf087827dd536481974eb28465ce4ce69cf13121589e4a36264ef2279e9d1d

deleted image sha256:5b713462d7ac7ebb468a1f850fa470c34f7b3aaa91ecb9a98b768e892c7625af

deleted image sha256:164f2f7ad057816d45349ffc9e04c361018cb8ce135accdc437f9c7b0cec7460

untagged image gcr.io/terrycho-personal/node-example:v0.32.0-28-g6bd1d50a

untagged image gcr.io/terrycho-personal/node-example@sha256:608ceeae6724e84d30ea61fcfedbaf94aedd3fb6dfbf38c97d244af8c65cbe54

deleted image sha256:d13071d2094092d50236db037ac7e75efca479ed899ec09e3a9a0b61789d93da

deleted image sha256:f9ff5b561a440490345672fe892eeedcd28e05dc01c4675fea8cf5f465b87898

untagged image gcr.io/terrycho-personal/node-example:v0.32.0-28-g6bd1d50a-dirty

untagged image gcr.io/terrycho-personal/node-example@sha256:0ba775dee9f33dc74cea1158f2bc85190649b8102991d653bc73a8041c572600

deleted image sha256:18c830d66ee7c7d508e4edef2980bd786d039706e9271bd2f963a4ca27349950

deleted image sha256:98d168285f8df5c00d7890d1bb8fcc851e8a9d8f620c80f65c3f33aecb540d28

untagged image gcr.io/terrycho-personal/node-example@sha256:0bb49fc5b4ff6182b22fd1034129aed9d4ca5dde3f9ee2dd64ccd06d68d7c0f8

deleted image sha256:a63e7f73658f4f69d7a240f1d6c0fa9de6e773e0e10ab869d64d6aec37448675

deleted image sha256:7e59e5acb2511a5a5620cf552c40bdcba1613b94931fe2800def4c6c6ac6b2a6

deleted image sha256:f7a7217f7a60b23ee04cf50a9bac3524d45d2fd14b851feedd3dc6ffac8b953f

deleted image sha256:b3f0702d41a7df01932c3d3f0ab852ceae824d8acc14f70b7d38e694caa67dc1

deleted image sha256:457e460aae8d185410b47b0a62e027a7d09c7e876cdfaa0c06bcaf6069812974

deleted image sha256:f8e24fe06e6e2c04b5bf29f6eb748d99107781333dc229d03ff77865c1937f8c

deleted image sha256:3b7878ac4c42f541efb80af06415bec286254912f8c3a64e671e7f2f3808b91b

deleted image sha256:49ac353388c6c1e248ac41262ffef47a26ada4d28955e90308c575407f316c64

deleted image sha256:7c725818faaeb6db8d5650e037dc4fa0f38a95bc858ea396f729dfa9ccdc1e06

deleted image sha256:ea8f04b01ca5be4b4fda950557526c3a0707ebda06d0e9392501cba21cc17327

WARN[1877] builder cleanup: pruning images: Error: No such image: sha256:18c830d66ee7c7d508e4edef2980bd786d039706e9271bd2f963a4ca27349950 

Cleaning up...

deployment.apps "node" deleted

Cleanup complete in 2.677585866s

There is a new version (0.32.0) of Skaffold available. Download it at https://storage.googleapis.com/skaffold/releases/latest/skaffo


본인은 구글 클라우드의 직원이며, 이 블로그에 있는 모든 글은 회사와 관계 없는 개인의 의견임을 알립니다.

쿠버네티스 패키지 매니저 Helm

#2-3. Charts (디렉토리 구조)

조대협 (http://bcho.tistory.com)

디렉토리 구조

Helm 차트의 디렉토리 구조는 다음과 같다. 직접 아래와 같은 디렉토리 구조에 파일을 각각 생성해도 되지만, 기본 템플릿을 helm create [차트명] 으로 생성할 수 있다.

아래는

%helm create mychart

명령으로 생성한 디렉토리의 구조이다.


mychart/

 Chart.yaml          # A YAML file containing information about the chart

 LICENSE             # OPTIONAL: A plain text file containing the license for the chart

 README.md           # OPTIONAL: A human-readable README file

 requirements.yaml   # OPTIONAL: A YAML file listing dependencies for the chart

 values.yaml         # The default configuration values for this chart

 charts/             # A directory containing any charts upon which this chart depends.

 templates/          # A directory of templates that, when combined with values,

                     # will generate valid Kubernetes manifest files.

 templates/NOTES.txt # OPTIONAL: A plain text file containing short usage notes

Chart.yaml 파일

Chart.yaml은 차트에 대한 기본적인 메타 정보를 정의 한다.

apiVersion: The chart API version, always "v1" (required)

name: The name of the chart (required)

version: A SemVer 2 version (required)

kubeVersion: A SemVer range of compatible Kubernetes versions (optional)

description: A single-sentence description of this project (optional)

keywords:

 - A list of keywords about this project (optional)

home: The URL of this project's home page (optional)

sources:

 - A list of URLs to source code for this project (optional)

maintainers: # (optional)

 - name: The maintainer's name (required for each maintainer)

   email: The maintainer's email (optional for each maintainer)

   url: A URL for the maintainer (optional for each maintainer)

engine: gotpl # The name of the template engine (optional, defaults to gotpl)

icon: A URL to an SVG or PNG image to be used as an icon (optional).

appVersion: The version of the app that this contains (optional). This needn't be SemVer.

deprecated: Whether this chart is deprecated (optional, boolean)

tillerVersion: The version of Tiller that this chart requires. This should be expressed as a SemVer range: ">2.0.0" (optional)

차트의 이름이나 제작자등이 들어가는데, 몇가지 중요한 필드들을 살펴보자.

  • name은 Helm 차트의 이름을 정의한다.

  • version을 정의하는 것은 두가지 필드가 있는데, version 필드가 있고, appversion 이라는 필드가 따로 있다. version 필드는 helm 차트 자체의 버전을 정의하고, appversion은 이 차트를 통해서 배포 되는 애플리케이션의 버전을 정의한다. 예를 들어 helm 차트를 처음 만들었으면, helm 차트 버전은 1.0.0이 되고, helm chart를 통해서 채팅 애플리케이션 1.2를 배포한다면 appVersion은 1.2가 된다. 버전에 대한 네이밍은 semver (https://semver.org/lang/ko/)을 따르도록 한다.

License 및 README 파일

License 파일에는 이 Helm 차트의 라이센스 그리고,README에는 간략한 설명들을 적어 놓는다.

requirement.yaml 파일

Requirement.yaml에는 이 helm 패키지를 설치하기 위해서 필요한 다른 차트들의 목록을 기술한다. 이렇게 기술된 차트들은 이 차트가 설치되기 전에 자동으로 설치된다.

requirement.yaml 파일의 구조는 다음과 같다.


dependencies:

 - name: apache

   version: 1.2.3

   repository: http://example.com/charts

 - name: mysql

   version: 3.2.1

   repository: http://another.example.com/charts


Requirement.yaml에는 이 차트에 대한 의존성을 정의하는데, 필요한 차트의 이름과 버전 그리고, 그 차트가 저장된 리파지토리 경로를 정의한다.

위의 예제를 보면 apache의 1.2.3 버전의 차트가 필요하고, 이 차트는 http://example.com/charts에 저장되어 있음을 알 수 있다. 리파지토리에 대해서는 뒤에서 다시 한번 자세히 설명하도록 한다.


참고로 의존성에 정의된 차트들을 모두 설치하게할 수 도 있지만, tag나 condition을 사용하여, 조건에 따라 설치를 조정할 수 있다. 예를 들어 node.js API서버와 MySQL로 되어 있는 2-tier 애플리케이션이 있을때, MySQL을 같이 설치하도록 의존성을 정해놓을 수 있지만, 이미 MySQL이 쿠버네티스 클러스터내에 설치되어 있을 경우에는 그것을 재활용하면 되기 때문에, 옵션에 따라서 MySQL을 설치하거나 설치하지 않도록 할 수 있다.

charts/ 디렉토리

의존성이 필요한 차트들을 requirement.yaml 에 정의해놓는 경우, requirement.yaml에 있는 경로에서 다운로드 받아서 설치하는데, 다운로드를 받게하지 않고,차트를 배포할때 아예 같이 묶어서 배포하고 싶을 경우에는  charts 디렉토리에 차트들의 파일을 저장해놓으면, 차트를 배포할때 charts 디렉토리에 있는 차트를 먼저 배포하게 된다.

templates/ 디렉토리

Template는 앞의 예제를 통해서  설명했듯이, Helm 차트의 탬플릿을 정의한다.  

values.yaml 파일

values.yaml에는 템플릿에 사용될 value 값들을 저장한다



본인은 구글 클라우드의 직원이며, 이 블로그에 있는 모든 글은 회사와 관계 없는 개인의 의견임을 알립니다.

Apt.ly를 이용한 데비안 리포지토리 생성


조대협 (http://bcho.tistory.com)




앞의 글에서 Jenkins + Maven 조합을 통해서 애플리케이션 설치 파일을 데비안 패키지로 패키징하는 방법에 대해서 알아보았다. 이제 이 패키지를 서버에 설치하는 방법을 살펴본다.

패키지를 설치하는 방법은 간단하게 데비안 패키지 파일을 설치하고자 하는 서버에 복사해놓은 다음에, sudo apt-get install을 이용해서 설치하는 방법도 있지만, 설치하고자 하는 서버마다 복사하기가 번거롭기 때문에 조금 더 쉬운 접근을 위해서 데비안 패키지 서버를 올리는 방법이 있다. 우리가 JDK나  node.js 등 다양한 유닉스 패키지를 apt-get 을 이용하여 설치가 가능한것은 미리 데비안 패키지 리파지토리 서버가 지정되어 있고, 그 서버내에 패키지들이 등록되어 있기 때문인데, 애플리케이션 패키지를 같은 방법으로 설치할 수 있게 하려면 애플리케이션 데비안 패키지 파일을 등록할 리포지토리 서버를 설정하면 된다.


지금까지 구현해온 파이프라인은 아래 그림과 같다

  1.  gitHub 로 부터 Jenkins가 자바 코드를 자바 코드를 당겨온다

  2. 이 코드를 Maven을 이용하여 빌드한다

  3. Maven은 코드 빌드가 끝나고 이를 데비안 패키지로 패키징 한다.

  4. Jenkins는 데비안 패키징 파일을 로컬 리포지토리인 apt.ly에 저장한다.

  5. Spinnaker에서 설치할때 데비안 패키지를 apt.ly에서 당겨서 설치한다.


앞의 글에서 까지 1,2,3과정까지 진행을 하였고, 이 글에서는 4번 과정을 구현할 예정이다.

다양한 오픈소스가 있지만, 플랫폼 종속성이 없고 손쉽게 설치가 가능한 apt.ly (www.aptly.info) 를 기준으로 설명을 하고자 한다.


apt.ly 설치

여기서 설명하는 설치는 데비안 리눅스 9을 기준으로 하여 설명한다.

설치는 앞의 글에서 설치한 Jenkins 서버에 그대로 설치하도록 한다.

설치 방법은 간단하다. www.aptly.info 사이트에서 설치 메뉴얼을 체크하여, 현재 사용하는 리눅스 버전에 맞는 바이너리를 wget을 이용해서 다운 로드 받은 후에, chmod +x 로 실행 권한만 주면 된다.


$ wget https://dl.bintray.com/smira/aptly/0.9.5/debian-squeeze-x64/aptly

$ chmod +x aptly


명령어가 설치 되었으면 리파지토리를 생성해야 한다.

리포지토리 생성 과 확인

리포지 토리 생성은 aptly repo crete {리포지토리 명} 을 입력하면 된다.  아래 명령은 “terry-repo”라는 이름의 리포지토리를 생성한것이다.

%./aptly repo create terry-repo


아래 명령은 terry-repo 라는 리포지토리에 대한 정보를 조회 하는 명령이다.

%./aptly repo show -with-packages terry-repo


아래는 실제 실행결과 인데, 테스트를 위해서 helloterry_1.0_all 이라는 패키지를 등록해놨기 때문에 하나의 패키지가 등록되서 보이는 것을 확인할 수 있다.



apt.ly 에 패키지 등록하기

리포지토리가 생성되었으면, maven 에서 빌드한 패키지를 apt.ly 리포지토리에 등록해보자

등록하는 방법은 aptly repo add -force-replace {리포지토리명} {데비안 패키지 파일명} 식으로 사용하면 된다.

아래는 terry-repo에 helloworld.deb 파일을 등록하는 명령이다.

%./aptly repo add -force-replace terry-repo helloworld.deb

apt.ly 리포지토리 퍼블리슁하기

패키지를 등록했으면 외부에서 억세스사 가능하도록 리포지토리 퍼블리쉬를 해야 하는데, 퍼블리쉬는 어떤 버전의 OS와 CPU 타입에 설치할 수 있는지등의 메타 정보를 함께 등록한다.

명령어 사용법은 aptly publish repo -distribution=”{OS 버전 정보}" -architecture=”{CPU 타입}” -skip-signing=true {리포지토리명}

식으로 사용한다.

원래 데비안 패키지를 외부로 배포를 할때는 패키지의 변경(원하지 않은)을 막기 위해서 패키지에 사이닝을 하는데, 여기서는 -skip-signing 을 이용하여 사이닝 단계를 건너뛰도록 하였다. 이 리파지토리는 외부에서 억세스하는 용도가 아니라 내부에서 CI/CD 파이프라인 단계에서만 사용되기 때문에 사이닝을 생략하였다.


아래 명령은 데비안 stretch 버전에 amd64 (intel CPU)에 terry-repo 이름으 리포지토리를 퍼블리슁한것이다.

%./aptly publish repo -distribution=stretch -architectures="amd64" -skip-signing=true terry-repo


apt.ly 서버 기동

퍼블리슁이 되었다고 당장 리포지토리를 접근 가능한것이 아니다. apt-get을 이용한 인스톨은 HTTP 프로토콜을 이용해서 접근하기 때문에 apt.ly 파일 저장소를 접근 가능하게 하는 웹서버를 올려야 한다.

간단한 방법으로는 aptly serve 명령어를 이용해서 웹서버를 올리는 방법이 있다.

아래 명령어 처럼 aptly serve -listen={IP:포트}를 적으면 된다.


% ./aptly serve -listen=:9090 > aptly.log &


이 보다는 제대로 서비스를 하기위해서는 웹서버에 올리는게 좋은데,

(참고 : https://www.spinnaker.io/guides/tutorials/codelabs/hello-deployment/)

% sudo apt-get install nginx

를 통해서 nginx 를 설치한 후에, /etc/nginx/sites-enabled/default 파일을 다음과 같이 편집한다.


server {
       listen 9999 default_server;
       listen [::]:9999 default_server ipv6only=on;
       root /var/lib/jenkins/.aptly/public;
       index index.html index.htm;
       server_name localhost;
       location / {
               try_files $uri $uri/ =404;
       }
}


이때 root에 aptly의 public 디렉토리를 명시해줘야 하는데, aptly를 설치한 디렉토리의 .aptly/public 이 되는게 일반적이다. 여기서는 /var/lib/jenkins 디렉토리 아래에 리포지토리를 만들었기 때문에 /var/lib/jenkins/.aptly/public 디렉토리를 홈 디렉토리로 설정하였다.


nginx 를 기동하면 http 9999번 포트로 데비안 패키지 서비스를 시작한다.

apt.ly 를 통한 패키지 설치

데비안 패키지 서버를 설치하고 패키지를 등록했으면 실제로 패키지를 다른 서버에서 인스톨 해보자

다른 서버에서 이 패키지 서버에 대한 정보를 알고 있어야 하는데 (서버 주소) 이 정보는 /etc/apt/sources.list 라는 파일에 아래와 같은 형태로 등록 되어 있다.


deb http://deb.debian.org/debian/ stretch main

deb-src http://deb.debian.org/debian/ stretch main

deb http://security.debian.org/ stretch/updates main

deb-src http://security.debian.org/ stretch/updates main

deb http://deb.debian.org/debian/ stretch-updates main

deb-src http://deb.debian.org/debian/ stretch-updates main


이 파일에 앞서 설정한 데비안 리포지토리 서버 (apt.ly) 서버의 주소와 정보를 입력해주면 된다.

만약 http://myserver-ip:9999 포트로 서버를 올렸다면 아래와 같은 정보를 /etc/apt/sources.list 에 추가해주면 된다.


deb http://myserver-ip:9999 stretch main


설정이 끝났으면

%sudo apt-get update

명령을 실행하면 아래와 같이 새로운 리포지토리에서 정보를 읽어오는 것을 확인할 수 있다.


모든 준비가 끝났다.

인스톤을 해보자. 인스톨은 sudo apt-get install을 이용하면 된다.

앞서 등록한 패키지 명이 helloterry 였기 때문에 간단하게 아래와 같이 sudo apt-get install helloterry 명령어를 실행하면 된다.



이외에도 유사한 툴로 pulp (https://docs.pulpproject.org/user-guide/introduction.html#what-pulp-can-do)

클라우드 서비스로는 cloudsmith.io (https://cloudsmith.io/)등이 있다. 

작은 규모의 팀이라면 관리 문제도 있으니 클라우드 서비스를 쓰는 것도 좋은 방안이 되지 않을까 한다.



본인은 구글 클라우드의 직원이며, 이 블로그에 있는 모든 글은 회사와 관계 없는 개인의 의견임을 알립니다.

Spinnaker #3

Hello Spinnaker

조대협 (http://bcho.tistory.com)


Spinnaker에 대한 개념 이해 및 설치가 끝났으면, 이제 간단한 애플리케이션을 배포해보자.

여기서 사용하는 애플리케이션은 node.js로 8080 포트에 “This is Default” 라는 메세지를 출력하는 간단한 애플리케이션이다. VM이 기동되면 자동으로 이 node.js 서버가 기동되도록 설정을 해놓은 VM이미지를 만들어놓았다. 만약에 같은 테스트를 하고자 한다면 간단한 애프리케이션을 만들어도 좋고, nginx나 apache 웹서버를 설치해놓은 이미지를 사용해도 좋다.

Create Application

먼저 node.js 클러스터를 배포할 애플리케이션을 정의한다. 아래 처럼 메뉴에서 애플리케이션을 선택한 후에, 우측 상단의 Action 메뉴에서 Create Appliaction 메뉴를 선택한다.



다음 애플리케이션 정보에 애플리케이션명을 “hellospinnaker”로 입력하고, 관리자 이메일을 입력한다.





Load Balancer 생성

애플리케이션이 생성되었으면, 애플리케이션에서 사용할 로드밸런서를 사용한다. 구글 클라우드에는 여러 타입의 로드 밸런서가 있지만, 설정이 쉬운 Network 로드 밸런서를 사용하겠다.

Network Load Balancer는 TCP/UDP를 지원하는 Pass through (IP가 바뀌지 않는다.) 방식의 L4 로드 밸런서로, 구글의 망가속 기능을 사용하지 않는 리전 단위의 로드 밸런서이다.



로드 밸런서 타입을 선택했으면 상세 정보를 입력한다.

  • region을 선택한다. 여기서는 일본 리전인 asia-northeast1을 선택하였다.

  • 다음 로드밸런서의 포트를 선택해야 하는데, Listener 부분에서 TCP 프로토콜을 선택하고, 입력 포트를 8080으로 선택한다.

  • 그리고 마지막으로 중요한것은 Health Check 부분을 명시해야 하는데, Health check는 HTTP를 사용하게 된다. HTTP/GET request를 이용하여 Health check를 할 서버의 HTTP URL과 Port를 지정해야 한다. node.js 서버가 8080 포트를 통해 서비스 하기 때문에 Health Check도 8080 포트에 “/” 디렉토리로 지정한다.





Server 생성

로드 밸런서 설정이 끝났으면 여기에 붙일 서버 그룹을 정의해야 한다. 서버그룹 정의는 Clusters 메뉴에서 가능한데, 먼저 Clusters 메뉴로 들어간후, 우측 상단의 Create Server Group 버튼을 클릭하여, 서버 그룹 생성 화면을 불러온다.





서버 그룹에 정보에서는 아래 그림과 같이 region을 선택하고, VM을 생성할때 사용할 Image를 선택한다. 이 예제에서는 앞서 설명한것 처럼 node.js 애플리케이션을 “simple-node-server-default-image”라는 이미지로 준비해놓았다.

다음 Load Balancers 메뉴에서 로드 밸런서를 선택한다. 로드 밸런서는 앞 단계에서 만든 “hellospinnaker” 를 선택한다.





다음으로는 인스턴스 타입을 선택한다. 인스턴스 타입은 먼저 Zone 을 선택해야 선택할 수 있다. Zone은 두개의 존 이상에 걸치도록 설정하기 위해서 “Distribute instance multiple zones” 체크 박스를 클릭하면 선택한 리전에서 두개 이상의 존에 걸쳐서 인스턴스가 생성된다.

그리고 인스턴스 타입을 선택한다. 아래에서는 n1-standard-2 인스턴스를 선택하였다.

마지막으로 Number of instances에 기동시킬 인스턴스 수를 지정한다. 여기서는 4개의 인스턴스를 기동하도록 하였다.




서버 기동 확인

모든 설정이 끝났으면, 인스턴스가 기동되는 것을 확인할 수 있다. 아래 그림과 같이 인스턴스가 정상적으로 올라오면 초록색으로 표시가 된다. 만약 문제가 있어서 인스턴스가 올라오지 않으면 붉은 색으로 표시된다. (대부분 실패 하는 경우는 HeartBeat 설정이 제대로 되어 있지 않는 경우가 많다.)




실제로 구글 클라우드 콘솔의  Compute Engine탭을 확인해 보면 아래와 같이 VM들이 생성 된것을 확인할 수 있다. VM이름은 hellospinnaker-vxxx 라는 이름으로 생성이 되는것을 확인할 수 있다.



테스트

그러면 제대로 작동을 하는지 확인해보자. 로드밸런서의 IP를 확인해야 하는데,  생성된 로드밸런서를 클릭하면 로드밸런서의 IP가 아래 그림과 같이 우측에 나타난다.



이 IP로, HTTP 8080 포트로 접속을 해보면 아래 그림과 같이 접속이 되는 것을 확인할 수 있다.



지금까지 Spinnaker에 대한 제일 간단한 사용방법을 알아보았다.

실제 운영 환경에서는 이런식으로 사용하는 경우는 드물고, github등의 코드 Repository에서 코드가 변경되면 이를 Jenkins 등을 이용하여 빌드하고, 패키징 한 후에, VM등에 배포하는 파이프라인을 거치게 된다.

다음 글에서는 이러한 파이프라인을 하나 만들어 보도록 하겠다.



본인은 구글 클라우드의 직원이며, 이 블로그에 있는 모든 글은 회사와 관계 없는 개인의 의견임을 알립니다.

Spinnaker #1 - 소개


Spinnaker

Spinnaker 는 넷플릭스에서 개발하여 오픈 소스화한 멀티 클라우드를 지원하는 Continuous Delivery Platform 이다. 구글 클라우드, 아마존, 마이크로소프트등 대부분의 메이져 클라우드를 지원하며, Kubernetes 나, OpenStack 과 같은 오픈소스 기반의 클라우드 또는 컨테이너 플랫폼을 동시에 지원한다.

시나리오

Spinnaker 의 특징은 멀티 클라우드 지원성뿐만 아니라, 오케스트레이션 파이프라인 구조를 지원한다 특징인데,  배포 단계는 여러개의 스텝이 복합적으로 수행되는 단계이기 때문에, 복잡한 워크 플로우에 대한


관리가 필요하다.

하나의 배포 시나리오를 통해서 오케스트레이션 파이프라인에 대해서 이해해보도록 하자

  • 코드를 받아서 빌드를 하고,

  • 빌드된 코드를 VM에 배포하여 이미지로 만든 후에, 해당 이미지를 테스트한다.

  • 테스트가 끝나면, Red/Black 배포를 위해서 새버전이 배포된 클러스터를 생성한 후에

  • 새 클러스터에 대한 테스트를 끝내고

  • 새 클러스터가 문제가 없으면 트래픽을 새 클러스터로 라우팅한다.

  • 다음으로는 구버전 클러스터를 없앤다.

각 단계에서 다음 단계로 넘어가기 위해서는 선행 조건이 필요하다. 예를 들어 이미지가 빌드가 제대로 되었는지 안되었는지, 새 클러스터가 제대로 배포가 되었는지 안되었는지에 대한 선/후행 조건의 확인 들이 필요하다.

Spinnaker에서는 이러한 오케스트레이션 파이프라인을 “파이프라인”이라는 개념으로 구현하였다. 파이프라인 흐름에 대한 예를 보면 다음과 같다.


위의 파이프라인은 이미지를 찾아서 Red/Black 배포를 위해서 Production에 새로운 이미지를 배포하고, Smoke 테스트를 진행한 후에, 구 버전을 Scale down 시키고, 소스를 태깅 한다. 이때 구 버전을 Destory 하기 전에, Manual Approval (사람이 메뉴얼로 승인) 을 받고 Destory 하는 흐름으로 되어 있다.


또한  각 단계별로 하위 테스크가 있는 경우가 있다. 예를 들어 새로운 클러스터를 배포하기 위해서는 클라우드 내에 클러스터 그룹을 만들고, 그 안에 VM들을 배포한 후에, VM 배포가 완료되면 앞에 로드 밸런서를 붙이고, Health check를 설정해야 한다. 그리고 설정이 제대로 되었는지 체크를 한다음에 다음 단계로 넘어간다.


이러한 개념을 Spinnaker에서는 Stage / Steps/ Tasks/ Operation 이라는 개념으로 하위 태스크를 구현하였다. 개념을 보면 다음과 같다.



파이프라인 컴포넌트

파이프라인은 워크 플로우 형태로 구성이 가능하다. 아래 그림은 파이프라인을 정의하는 화면의 예시이다.


<그림. 파이프라인 예제>

출처 http://www.tothenew.com/blog/introduction-to-spinnaker-global-continuous-delivery/


파이프라인에서 스테이지별로 수행할 수 있는 테스크를 선택할 수 있다.  샘플로 몇가지 스테이지를 보면 다음과 같다.

  • Bake : VM 이미지를 생성한다.

  • Deploy : VM 이미지 (또는 컨테이너)를 클러스터에 배포한다.

  • Check Preconditions : 다음 단계로 넘어가기전에 조건을 체크한다. 클러스터의 사이즈 (EX. 얼마나 많은 VM이 생성되서 준비가 되었는지)

  • Jenkins : Jenkins Job 을 실행한다.

  • Manual Judgement : 사용자로 부터 입력을 받아서 파이프라인 실행 여부를 결정한다

  • Enable/Disable Server Group : 이미 생성된 Server Group을 Enable 또는  Disable 시킨다

  • Pipeline : 다른 파이프라인을 수행한다.

  • WebHook : HTTP 로 다른 시스템을 호출한다. 통상적으로 HTTP REST API를 호출하는 형


개념 구조


Spinnaker는 리소스를 관리하기 위해서, 리소스에 대한 계층구조를 정의하고 있다.



<그림. Spinnaker의 자료 구조 >

출처 : ttp://www.tothenew.com/blog/introduction-to-spinnaker-global-continuous-delivery/



가장 최상위에는 Project, 다음은 Application 을 가지고 있고, Application 마다 Cluster Service를 가지고 있고, 각 Cluster Service는 Server Group으로 구성된다. 하나하나 개념을 보자면,


Server Group 은, 동일한 서버(같은 VM과 애플리케이션)로 이루어진 서버군이다. Apache 웹서버 그룹이나 이미지 업로드 서버 그룹식으로 그룹을 잡을 수 도 있고, 이미지 서버 그룹 Version 1, 이미지 서버 그룹 Version 2 등으로 버전별로 잡는등 유연하게 서버군집의 구조를 정의할 수 있다.

이러한 서버 그룹은 Cluster 라는 단위로 묶일 수 있다.


아래 예제 그림을 통해서 개념을 좀더 상세하게 살펴보자


위의 그림은 이미지 서비스(Image service)를 제공하는 서비스를 Cluster로 정의한것이다.

위의 구조는 Image Service를 Service Group으로 정의했는데, v1,v2,v3 버전을 가지고 있고 각 버전이 Service Group으로 정의된다 (이런 이유는 멀티 버전을 이용한 카날리 테스트나 Red/Black 배포를 이용하기 위해서 여러 버전을 함께 운용하는 경우가 생긴다.)

그리고, 리전별로 별도의 Image Service를 각각 배포하는 모델이다.

리전과 멀티 클라우드의 개념은 Spinnaker 문서에 나온 자료 구조 이외에, 중요한 자료 구조인데, 리소스를 정의할때 클라우드 계정을 선택함으로써 클라우드를 선택할 수 있고, 서비스의 종류에 따라 리전을 선택하는 경우가 있는데 이 경우 리전별로 리소스를 분류해서 보여준다.


Cluster는 Application 내에서 생성될때 , Service Group을 생성시 입력하는  {Account}-{stack}-{Detail} 을 식별자로하여 Cluster를 식별한다. 같은 식별자를 가진 Service Group을 하나의 Cluster로 묶는다.

아래는 Service Group을 생성하는 화면으로 Account, Stack, Detail을 입력하는 메뉴가 있는 것을 확인할 수 있다.



아래 그림은 myapplication 이라는 이름을 갖는 Application 내에, 각각 MY-GOOGLE-ACCOUNT라는 account를 이용하여, myapplication-nodestack-cluster1과, myapplication-nodestack-cluster2 두개의 클러스터를 생성한 예제이다.





또는 자주 쓰는 구성 방식중 하나는 Red/Black (또는 Blue/Green  이라고도 함) 형태를 위해서 하나의 클러스터에 구버전과 새버전 서버 그룹을 각각 정의해놓고 구성하는 방법이 있다.


Application은 Cluster의 집합이고, Project는 Application의 집합이다.

개발하고 배포하고자 하는 시스템의 구조에 따라서 Project, Application, Cluster를 어떻게 정의할지를 고민하는 것이 중요하다.


예를 들어 하나의 서비스가 여러개의 애플리케이션으로 구성되어 있는 경우, 예를 들어 페이스북 처럼, 페이스북 앱, 웹 그리고 앱 기반 페북 메신져가 있는 경우에는 페이스북이라는 프로젝트 아래, 페이스북 앱 백앤드, 웹 백앤드, 앱 백앤드로 Application을 정의할 수 있고,각각의 Application에는 마이크로 서비스 아키텍쳐 (MSA) 방식으로 각각서 서비스를 Cluster로 정의할 수 있다.

아키텍쳐

마지막으로 Spinnaker의 내부 아키텍쳐를 살펴보도록 하자.

Spinnaker는 MSA (마이크로 서비스 아키텍쳐) 구조로 구성이 되어 있으며, 아래 그림과 같이 약 9 개의 컴포넌트로 구성이 되어 있다.



각 컴포넌트에 대해서 알아보도록 하자


  • Deck : Deck 컴포넌트는 UI 컴포넌트로, Spinnaker의 UI 웹사이트 컴포넌트이다.

  • Gate : Spinnaker는 MSA 구조로, 모든 기능을 API 로 Expose 한다, Gate는 API Gateway로, Spinnaker의 기능을 API로 Expose 하는 역할을 한다.

  • Igor : Spinnaker는 Jenkins CI 툴과 연동이 되는데, Jenkins에서 작업이 끝나면, Spinnaker Pipeline을 Invoke 하는데, 이를 위해서 Jenkins의 작업 상태를 Polling을 통해서 체크한다. Jenkins의 작업을 Polling으로 체크 하는 컴포넌트가 Igor이다.

  • Echo : 외부 통신을 위한 Event Bus로, 장애가 발생하거나 특정 이벤트가 발생했을때, SMS, Email 등으로 notification을 보내기 위한 Connector라고 생각하면 된다

  • Rosco : Rosco는 Bakering 컴포넌트로, Spinnaker는 VM또는 Docker 이미지 형태로 배포하는 구조를 지원하는데, 이를 위해서 VM이나 도커 이미지를 베이커링(굽는) 단계가 필요하다. Spinnaker는 Packer를 기반으로 하여 VM이나 도커 이미지를 베이커링 할 수 있는 기능을 가지고 있으며, Rosco가 이 기능을 담당 한다.

  • Rush : Rush는 Spinnaker에서 사용되는 스크립트를 실행하는 스크립트 엔진이다.

  • Front50 : Front 50은 파이프라인이나 기타 메타 정보를 저장하는 스토리지 컴포넌트이다.

  • Orca : Oraca는 이 모든 컴포넌트를 오케스트레이션하여, 파이프라인을 관리해주는 역할을 한다.

  • CloudDriver : 마지막으로 Cloud Driver는 여러 클라우드 플랫폼에 명령을 내리기 위한 아답터 역할을 한다.



본인은 구글 클라우드의 직원이며, 이 블로그에 있는 모든 글은 회사와 관계 없는 개인의 의견임을 알립니다.


Packer와 Ansible을 이용하여, node.js 이미지 생성하기


조대협 (http://bcho.tistory.com)


앞서 글에서 패커를 이용한 이미지 생성 및, 이미지 타입(http://bcho.tistory.com/1226) 에 대해서 알아보았다. 이번 글에서는 node.js 가 깔려있는 파운데이션 타입의 구글 클라우드 VM이미지를 패커와 앤서블을 이용해서 구현해 보도록 한다. 이 글을 이해하기 위해서는 http://bcho.tistory.com/1225 에 대한 이해가 필요하다.


구성은 다음과 같다. 패커를 이용하여, Debian OS 기반의 이미지를 만든 후에, 패커의 Provisioner를 이용하여 Ansible을 설치하고, 이 설치된 Ansible을 이용하여 node.js등을 설치하는 playbook 을 실행하는 순서로 node.js용 이미지를 만든다.  



패커 스크립트는 다음과 같다.

builder 부분은 예전과 같다.(http://bcho.tistory.com/1225) Debian 이미지를 기반으로 VM을 생성한다.

VM 생성후에, 소프트웨어 설치등을 정의하는 부분은 provisioner 라는 부분에 정의되는데, 두 타입의 Provisioner가 사용되었다. 첫번째는 shell 타입이고 두번째는 ansible-local 형태의 provisioner이다.


{

 "variables":{

   "project_id":"terrycho-sandbox",

   "prefix":"debian-9-nodejs"

 },

 "builders":[

  {

   "type":"googlecompute",

   "account_file":"/Users/terrycho/keys/terrycho-sandbox-projectowner.json",

   "project_id":"{{user `project_id`}}",

   "source_image":"debian-9-stretch-v20180105",

   "zone":"us-central1-a",

   "ssh_username":"ubuntu",

   "image_name":"{{user `prefix`}}-{{timestamp}}",

   "machine_type":"n1-standard-4"

  }

 ],

 "provisioners":[

   {

     "type":"shell",

     "execute_command":"echo 'install ansible' | {{ .Vars }} sudo -E -S sh '{{ .Path }}'",

     "inline":[

               "sleep 30",

               "apt-add-repository ppa:rquillo/ansible",

               "/usr/bin/apt-get update",

               "/usr/bin/apt-get -y install ansible"

               ]

   },

   {

      "type":"ansible-local",

      "playbook_file":"./nodejs_playbook.yml"

   }


 ]


}


첫번째 provisioner에서는 ansible을 apt-get으로 설치하기 위해서 sudo 권한으로 apt-get update를 실행하여, 리파지토리 정보를 업데이트 한후에, apt-get -y install ansible을 이용하여, ansible을 설치한다.


두번째 provisioner는 ansible-local provisioner로, 앞단계에서 설치된 ansible을 로컬에서 실행하여, playbook을 실행해주는 코드이다.

ansible은 Configuration management & Deployment 도구로, 나중에 기회가 되면 다른글을 이용해서 소개하도록 한다.

이 코드에서 호출된 nodejs_playbook.yml 파일의 내용은 다음과 같다.

- hosts: all

 tasks:

       - name : create user node

         become : true

         user :

             name: nodejs

             state : present

       - name : update apt-get install

         shell : curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -

       - name : install node.js LTS

         become : true

         #become_user: nodejs

         apt : pkg=nodejs state=installed update_cache=true


hosts:all로, ansible에 등록된 모든 호스트에 대해서 스크립트를 실행하도록 한다. 여기서는 별도의 호스트를 등록하지 않았고, ansible-local 타입으로 실행하였기 때문에, 이 호스트 (localhost)에만 스크립트가 실행된다.

크게 3단계로 실행이 되는데, 첫번째가 nodejs라는 사용자를 만드는 단계로, user 라는 모듈을 사용하여 nodejs라는 사용자를 생성하였다. 이 사용자 계정은 향후 애플리케이션이 배포되었을때, nodejs를 실행할 계정으로 사용된다. 사용자 계정을 만들기 위해서는 root 계정을 획득해야하기 때문에, become: true로 하여 sudo 로 명령을 실행하도록 하였다.

두번째는 node.js를 인스톨하기 위해서 설치전 사전 스크립트를 실행하는 부분이다. apt-get install을 디폴트 상태에서 실행하게 되면 node.js 4.x 버전이 인스톨된다. 최신  8.X 버전을 인스톨하기 위해서, 스크립트를 실행한다. 앤서블 모듈중에서 shell 모듈을 이용하여 쉘 명령어를 실행하였다.

세번째 마지막은 apt 모듈을 이용하여, node.js를 인스톨하도록 한다.


스크립트 작업이 끝났으면, 이미지를 생성해보자

%packer build node.json


으로 실행을 하면 이미지가 생성된다. 생성된 이미지는 구글 클라우드 콘솔의 GCE (Google Compute Engine)의 Images 메뉴에서 확인이 가능하다.

다음과 같이 debian-9-nodejs-*로 새로운 이미지가 생성된것을 확인할 수 있다.



생성된 이미지가 제대로 되었는지를 확인하기 위해서, 이 이미지로 VM을 생성해서 nodejs 버전을 확인해보면 다음과 같이 8.9.4 가 인스톨 되었음을 확인할 수 있다.

또한 nodejs로 된 계정이 생성되었는지를 확인하기 위해서 /etc/passwd 내에 사용자 정보가 생성되었는지를 확인해보면 아래와 같이 nodejs 이름으로 계정이 생성되었음을 확인할 수 있다.



참고 : https://blog.codeship.com/packer-ansible/


본인은 구글 클라우드의 직원이며, 이 블로그에 있는 모든 글은 회사와 관계 없는 개인의 의견임을 알립니다.

오랜만의 포스팅입니다. 그간 많이 바뻤습니다.

요즘 시스템 운영쪽에 관심이 많아서 Devops (Development + Operation)쪽을 틈틈이 보고 있습니다. 오늘은 조직의 성숙도별 개발 모델과 함께, CD (Continuous Delivery)와 Devops에 대해서 설명해보고자 합니다.



회사의 규모나 성숙도에 따라서 개발 모델을 크게 다음과 같이 3단계로 나눠볼 수 있습니다.


1. 스타트업


소규모에 처음 서비스 개발을 시작한 스타트업 기업 같은 경우에는 일단 모든 의사 결정이 빠르다. 아이디어가 나오면 별도의 승인이나 분석 없이 바로 개발하고, 개발이 끝나면 바로 배포 한다. 규모가 작고 모든 의사 결정이 팀내에서 이루어지기 때문에 매우 빠르다. 그리고 인력이 적기 때문에, 분석/설계/개발 및 운영이 같은 그룹에서 이루어진다.


2. 성숙된 개발 조직

어느정도 조직이 성숙되고, 인원이 많아지고 이익에 대해서 고민을 하게 되면, 조금더 체계화된 개발 프로세스를 원하게 된다.

한정된 예산으로 서비스를 개발하게 되며, 인원이 많아짐으로써 품질 저하를 막기 위해서 역할이 세분화 되고 체계화 된다.




아마 대부분의 일반적인 서비스 개발 이나 시스템 개발 기업들은 이러한 프로세스를 따를 것이다. 아이디어가 나오면, 발표하고 경영진을 설득하여 예산을 정하고, 요구 사항 분석을 통해서 범위를 정한후, 개발/테스트/수정을 한후, 모든 테스트를 통과하면 릴리즈 일정을 결정하고 산출물을 정리한후, 운영팀으로 이행한다.


체계화 되어 있기는 하지만, 앞서 설명한,Start up에 비해서는 전체적인 프로세스가 느리고, 운영으로 이관후, 자잘한 Fix나 Enhancement가 어렵다. 그리고, 새로운 기능이나 컴포넌트를 개발하려면, 새로운 프로젝트를 시작해서 위와 같은 전과정을 다시금 거쳐야 한다.


3. CD와 Devops기반의 개발 모델

요즘 같이 새로운 서비스가 많이 나오는 시절에, 저런형태의 개발 프로세스는 빠른 기능 추가등이 불가능하고, 운영중의 피드백을 받기 어려워서 SNS와 같은 서비스에는 적절하지가 않다. 

그래서 CD (Continuous Delivery)와 Devops라는 개념을 사용하는데


CD

CD는 Continuous Delivery의 약어로

운영 시스템에 계속해서 Fix나 새로운 기능을 지속적으로 Release를 하는 개념이다.

쉽게 예를 들어보면, 프로젝트 기간이 끝나면 릴리즈를 하는게 아니라, 매일매일 새로운 FIX나 기능이 추가되면 거의 매일 릴리즈를 하는 개념으로 보면 된다.

Face Book의 경우 이런식으로 매일 개발자가 새로운 기능을 운영 환경에 반영 및 추가 하는 것으로 알고 있다.


Devops

Devops는 Netflix에서 주로 시작된 개념으로 개발팀과 운영팀을 하나로 묶어서, 커뮤니케이션에서 오는 장애를 해소하고 빠른 서비스 개발과 반영을 하고자 함에 있다.

보통 개발팀과 운영팀이 나눠져 있는 것이 전통적인 모델인데, 이 경우에는 운영중에 고객의 요구 사항등이 개발쪽에 잘 전달되지 않고, 매일 서비스를 운영하면서 개선 사항이 있더라도 개발팀에 전달되기가 어려운 경우가 많다. 반대로, 개발쪽에서 무엇인가를 수정하면 수정 내용이 운영쪽에 제대로 전달되지 않아서 배포 실수등을 유발하여 시스템 장애를 유발하는 경우가 많다

그래서 Devops는 두팀을 하나로 합침으로써, 서로간의 의사소통을 빠르게 하고, 개발자가 직접 운영환경을 컨트롤함으로써 빠른 피드백을 받고, 빠른 반영을 통해서 서비스의 신속성을 향상 시키는 모델이다.


보통 이런 개념을 채용한 개발 모델은 다음과 같다.


위의 그림은 TDD를 채용한 그림인데, 먼저 테스트 계획서를 작성한후, 개발 및 테스트를 수행한후, 운영환경에 배포하고, 모니터링을 한다. 그리고 바로 신규 기능에 대한 피드백이나 효과를 모니터링해서 다시금 요구 사항을 정의하는 형태를 따른다

이렇게 Devops와 CD를 적용하기 위해서 중요한것은 자동화된 툴셋이 매우 중요하다.

개발 반영시 자동으로 꼼꼼하게 테스트를 할 수 있어야, 운영시 발생하는 장애를 방지할 수 있으며, 위의 전체 프로세스의 주체는 개발자가 되기 때문에, 복잡한 인프라나 미들웨어에 대한 배포를 자동으로 할 수 있어야 한다.

물론 자동화 툴셋은 어디까지나 구현 관점이다. 더 중요한것은 문화적인 차이점을 이해해야 하고, 프로세스의 변화를 인지하고 바꿔야 한다.

기존의 조직처럼 운영과 개발이 나눠져 있는 경우 조직을 합친다는 것은 기존에 가지고 있는 프로세스,  조직 구조를 모두 바꿔야 하는 것을 의미하며, 아울러서 기술셋도 모두 바꿔야 한다.

아울러 예산 집행 방식에 있어서도 기존에는 개발비용과 운영 비용을 나눠서 미리 잡아놓고 집행했기 때문에 초기 투자비와 운영비용(Running Cost또는 Opex), Devops 방식은 있는 인원들이 쭈욱 업그레이드와 운영을 계속해서 나가는 방식이라서 초기 투자비용보다는 운영비용(Running Cost)에 대한 부분이 커진다.


Devops나 CD의 경우에는 분명히 서비스 관점에서 가지는 이득은 매우 많지만, 변화의 폭이 기존 개발 방식에 비해서 매우 크기 때문에, 함부로 달려들 것은 아니라고 본다. 

그러나 스타트 업에서 규모가 커질 경우 위에서 언급한 정형화된 프로세스보다는 CD/Devops기반의 개발 프로세스로 비교적 쉽게 이동할 수 있고 얻는 이득도 많다.

기존의 대기업이나 SI기업의 경우에는 Devops 모델을 도입하기에는 변경되어야 하는 부분이 매우 많기 때문에, 매우 신중한 접근이 요구 된다.


본인은 구글 클라우드의 직원이며, 이 블로그에 있는 모든 글은 회사와 관계 없는 개인의 의견임을 알립니다.