Contents

Ubuntu 패키지 저장소 만들기

우분투 리눅스 운영체제는 데비안으로 부터 파생된 .deb 기반의 패키지 관리 시스템을 가지고 있다. 나는 현재 개발 중인 애플리케이션을 효율적으로 배포하기 위해서 프라이빗 패키지 저장소를 만들기로 했다. 배포 프로세스는 다음과 같다.

 배포프로세스
  1. Git을 이용해서 개발한다.
  2. CI툴은 git에서 코드를 가져와서 최신 빌드를 만든다.
  3. 빌드가 끝나면 .deb 로 만들어서 패키지 저장소에 복사한다.
  4. apt-get을 이용해서 패키지를 설치한다.

deb 패키지 만들기

현재 시간을 출력하는 간단한 웹 애플리케이션을 만들고, 이것을 deb 패키지로 만들어서 배포하려고 한다.

테스트 애플리케이션

테스트용 go 애플리케이션이다. 애플리케이션의 이름은 mydate다.
package main

import (
    "fmt"
    "net/http"
    "time"
)

func date(w http.ResponseWriter, r *http.Request) {
    t := time.Now()
    fmt.Fprintf(w, t.Format(time.RFC3339))
}
func main() {
    http.HandleFunc("/time", date)
    http.ListenAndServe("localhost:8082", nil)
}

애플리케이션의 빌드 디렉토리는 /home/workspace/mydate다. 아래와 같이 구성했다.
/home/workspace/mydata
                 ├── dist
                 │   └── mydate
                 ├── Makefile
                 └── mydate.go
  • mydata : mydata.go와 Makefile등 프로젝트를 위한 주요 파일들을 배치한다.
  • mydata/dist : .deb 배포파일을 만들기 위한 루트 디렉토리다.
  • mydata/dist/mydate : .deb 파일이 만들어지는 작업 디렉토리다. .deb 파일을 위한 메타파일, 패키징에 포함될 각종 파일들이 위치한다.

애플리케이션 구성

제일 먼저 애플리케이션 배포를 위한 디렉토리를 설계했다.
/
├── etc
│   └── init.d
└── opt
    └── joinc
        ├── bin
        └── conf
  • /etc/init.d : mydate init.d 스크립트가 위치한다. 빈파일 하나 만들어서 테스트 한다.
  • /opt/joinc : joinc에서 배포하는 모든 애플리케이션에 여기에 복사한다.
  • /opt/joinc/bin : mydata 실행 파일을 복사한다.
  • /opt/joinc/conf : mydata 설정 파일을 복사한다. 설정파일의 이름은 mydata.ini다. 빈파일로 테스트 한다.

패키징을 위한 디렉토리 생성

앞서 만든 애플리케이션 구성 계획에 따라서 패키징 디렉토리를 만든다. dist/mydate밑에 DEBAIN 디렉토리와 앞서 설계한 배포 디렉토리를 만들었다. 현재 디렉토리 구조는 아래와 같다.
/mydate
 ├── dist
 │   └── mydate
 │       ├── DEBIAN
 │       ├── etc
 │       │   └── init.d
 │       └── opt
 │           └── joinc
 │               ├── bin
 │               └── conf
 ├── Makefile
 └── mydate.go

control 파일 만들기

DEBIAN 디렉토리 밑에는 패키징 정보를 담고 있는 control 파일이 위치한다. dpkg 명령은 control 파일을 읽어서 .deb 파일을 만든다. deb 패키징을 위한 핵심 파일이라고 할 만하다. 아래는 mydate를 위한 control 파일예제다.
Package: mydate 
Version: 1.0.3
Section: devel
Priority: optional
Architecture: amd64 
Depends: cmake, sqlite3 
Recommends: libsqlite3-dev
Maintainer: yundream <yundream@gmail.com>
Homepage: http://www.joinc.co.kr
Description: mydate web api server 
 1.0.1 : message #1 
 1.0.2 : message #2 
 1.0.3 : message #3 
Package 이름은 mydate이고, 버전은 1.0.3이며 amd64 아키텍처에서 작동하는 프로그램이라는 걸 지시하고 있다. Description에는 애플리케이션의 설명과 최근 변경 정보를 설정했다.

이제 배포할 파일들을 dist/mydate 밑에 있는 각 디렉토리에 복사하자.
# cp mydate dist/mydate/opt/joinc/bin
# cp mydate.ini dist/mydate/opt/joinc/conf
# cp mydate.init dist/mydate/etc/init.d/mydate 

.deb 패키지 생성

dpkg로 deb 패키지를 만들어 보자. dist 디렉토리로 이동 한 다음 아래 명령을 실행한다.
# dpkg -b mydate
dpkg-deb: `mydate' 패키지 빌드하는 중입니다 (`mydate.deb'에서).
# ls 
mydate  mydate.deb
mydate/DEBIAN/control 내용을 읽어서 mydate.deb 파일을 만들었다. dpkg로 패키지 정보를 확인 할 수 있다.
# dpkg -I mydate.deb 
 new debian package, version 2.0.
 size 1655280 bytes: control archive=344 bytes.
     313 bytes,    13 lines      control              
 Package: mydate 
 Version: 1.0.3
 Section: devel
 Priority: optional
 Architecture: amd64 
 Depends: cmake, sqlite3 
 Recommends: libsqlite3-dev
 Maintainer: yundream <yundream@gmail.com>
 Homepage: http://www.joinc.co.kr
 Description: mydate web api server 
  1.0.1 : message #1 
  1.0.2 : message #2 
  1.0.3 : message #3 

.deb 패키지 설치및 삭제

mydate.deb를 설치해보자.
# sudo dpkg -i mydate.deb 
[sudo] password for yundream: 
Selecting previously unselected package mydate.
(데이터베이스 읽는중 ...현재 290768개의 파일과 디렉터리가 설치되어 있습니다.)
Preparing to unpack mydate.deb ...
Unpacking mydate (1.0.3) ...
mydate (1.0.3) 설정하는 중입니다 ...
Processing triggers for systemd (225-1ubuntu9.1) ...
Processing triggers for ureadahead (0.100.0-19) ...
ureadahead will be reprofiled on next reboot
-L 옵션으로 설치한 파일들을 확인할 수 있다.
# dpkg -L mydate
/.
/etc
/etc/init.d
/etc/init.d/mydate
/opt
/opt/joinc
/opt/joinc/bin
/opt/joinc/bin/mydate
/opt/joinc/conf
/opt/joinc/conf/mydate.ini

-r 명령으로 삭제 할 수 있다.
# dpkg -r mydate
(데이터베이스 읽는중 ...현재 290773개의 파일과 디렉터리가 설치되어 있습니다.)
Removing mydate (1.0.3) ...
Processing triggers for systemd (225-1ubuntu9.1) ...
Processing triggers for ureadahead (0.100.0-19) ...

원격 apt 패키지 저장소 만들기

격에 패키지 저장소를 만들기로 했다. 구축이 된다면, apt-get명령을 이용해서 패키지의 설치, 업그레이드, 삭제등을 수행 할 수 있다. 특히 Chef등의 프로비저닝 툴과 통합하면 높은 완성도의 애플리케이션 관리 시스템을 구축할 수 있다.

패키지 저장소의 IP 주소는 192.168.56.5다.

웹 서버 설치 및 패키지 디렉토리 생성

apt 패키지 시스템은 HTTP프로토콜을 사용한다. NginX웹 서버를 설치했다.
# apt-get install -y nginx
NginX의 루트 디렉토리는 /var/www/html 이다. 여기에 deb 패키지 파일을 복사하기 위해서 stabledevel 디렉토리를 만들었다. stable 디렉토리는 테스트와 안정화가 끝난 릴리즈 단계의 패키지, devel 에는 테스트 중인 패키지가 복사된다.
# cd /var/www/html
# mkdir -p joinc/stable
# mkdir -p joinc/devel

apt 저장소 업데이트

앞서 만든 mydate.deb 파일을 joinc/stable로 복사한다.
# cp mydate.deb /var/www/html/joinc/stable/

dpkg-scanpackages 명령으로 패키지 정보를 담고 있는 Packages.gz파일을 만들었다. 패키지를 사용하는 측은 이 파일을 읽어서, 패키지목록을 만든다.
# cd /var/www/html/joinc
# dpkg-scanpackages stable /dev/null | gzip -9c > stable/Packages.gz
dpkg-scanpackages: warning: Packages in archive but missing from override file:
dpkg-scanpackages: warning:   mydate
dpkg-scanpackages: info: Wrote 1 entries to output Packages file.

zcat으로 Packages.gz 파일을 보면, control 파일에 있던 내용들이 그대로 담겨있는 걸 알 수 있다.
# zcat stable/Packages.gz 
Package: mydate
Version: 1.0.3
Architecture: amd64
Maintainer: yundream <yundream@gmail.com>
Depends: cmake, sqlite3
Recommends: libsqlite3-dev
Filename: stable/mydate.deb
Size: 1655280
MD5sum: cc3ae6b4c782b7edea7c25ed0525575a
SHA1: 639a44b621dcbda773dd768a7ef26014e4cc43e7
SHA256: 4d880e5b1ebf58cf83b1487d636918853e6dc4a299746414756f526d37cc3833
Section: devel
Priority: optional
Homepage: http://www.joinc.co.kr
Description: mydate web api server
 1.0.1 : message #1
 1.0.2 : message #2
 1.0.3 : message #3

apt-get을 이용해서 패키지 인스톨 하기

인스턴스를 하나 만든 다음 apt-get을 이용해서 mydate 패키지를 설치해보자. /etc/apt/sources.list에 저장소를 추가하고 update 한다.
# cat /etc/apt/sources.list
deb http://192.168.56.1/joinc/ stable/
# apt-get update
apt-cache로 패키지를 확인 할 수 있다.
# apt-cache search mydate
mydate - mydate web api server
install 명령으로 패키지를 설치할 수 있다.
# apt-get install mydate
패키지 목록을 읽는 중입니다... 완료
의존성 트리를 만드는 중입니다       
상태 정보를 읽는 중입니다... 완료
다음 패키지를 더 설치할 것입니다:
  libsqlite3-dev
제안하는 패키지:
  sqlite3-doc
다음 새 패키지를 설치할 것입니다:
  libsqlite3-dev mydate
0개 업그레이드, 2개 새로 설치, 0개 제거 및 85개 업그레이드 안 함.
2,143 k바이트 아카이브를 받아야 합니다.
이 작업 후 1,616 k바이트의 디스크 공간을 더 사용하게 됩니다.
계속 하시겠습니까? [Y/n] Y
경고: 다음 패키지를 인증할 수 없습니다!
  mydate
확인하지 않고 패키지를 설치하시겠습니까? [y/N] 

control 파일 관리

control 파일로 부터 deb파일을 만들고 apt 저장소에 올리는 방법까지 살펴봤다. 하지만 실제 프로젝트에서 사용하려면 control 파일을 자동으로 관리 할 수 있어야 한다. 즉
  1. 패키지의 버전을 관리 해야 한다. control 파일의 Version 필드를 수작업으로 변경 할 수는 없는 노릇이다.
  2. description에 있는 메시지도 자동으로 관리해야 한다. git의 가장 최근 로그를 남기겨야 할 테다.
등의 작업을 해야 한다.

버전과 로그 관리

git 버전과 연동하면 된다.

tag 명령을 이용해서, 현재 코드에 버전을 붙인다.
# git tag -a 1.0 -m "function a edit"
# git push
이제 코드를 commit 할 때마다 버전정보가 갱신이 된다. 현재 버전 정보는 git describe --tags 로 읽을 수 있다.
# git describe --tags
1.0-1-gdf2d7ed
이렇게 읽어온 버전 정보로 control 파일을 만들면 된다.

로그도 git 명령으로 읽어올 수 있다.
# git log -5 --pretty=format:"%ci %h %s"
2016-05-22 01:00:51 +0900 df2d7ed README 추가
2016-03-25 12:05:41 +0900 eddd1d1 Debug #1 
2016-02-16 13:52:38 +0830 73b29b8 Debug #2 
2016-02-16 13:40:16 +0830 36faae2 Debug #3 
2016-02-16 12:25:13 +0830 7278adb Debug #4 
이 내용으로 control 파일의 Description을 만들 면 된다.

Makefile

control 파일을 만들고, control 파일로 부터 deb를 만드는 등의 작업은 Makefile로 자동화 할 수 있다. 대략 아래와 같은 느낌이 될 거다.
# cat Makefile
default: build
# 버전 정보를 읽어온다.
Version=$(shell git describe --tags)
devDir=/var/www/html/joinc/stable
releaseDir=/var/www/html/joinc/devel

build:
    go build -ldflags "-X main.Version=$(Version)"

deb:
    # build를 끝낸 실행파일과 설정파일 등을 배포 디렉토리로 복사한다. 
    # control 템플릿 파일로 부터 DEBIAN/control 파일을 만든다.
    # dpkg -b 로 deb 패키지를 만든다.
    # deb 패키지를 devel 디렉토리로 복사한다. 
    # dpkg-scanpackages으로 Packages 파일을 만든다.

release:
    # 최신 버전의 deb 패키지를 stable 디렉토리로 복사한다. 
    # dpkg-scanpackages으로 Packages 파일을 만든다.
control 템플릿 파일은 아래와 비슷할 것이다. go에서 제공하는 템플릿을 기준으로 만들었다.
Package: mydate 
Version: {{.Version}} 
Section: devel
Priority: optional
Architecture: amd64 
Depends: cmake, sqlite3 
Recommends: libsqlite3-dev
Maintainer: yundream <yundream@gmail.com>
Homepage: http://www.joinc.co.kr
Description: mydate web api server 
{{.Log}}
나는 git 레포지토리 정보를 읽어서 Version과 Log를 가져와서 템플릿파일로 부터 control을 만드는 간단한 프로그램을 만들었다. 이제 Jenkins와 같은 CI 툴과 연동하면, 완전한 배포 시스템을 만들 수 있다.

젠킨스와의 통합

Makefile까지 만들었다면 간단하게 젠킨스와 통합할 수 있다. 나는 아래와 같이 빌드환경을 구성했다.
  1. build 와 deb 룰만을 수행하기 위한 작업과 release룰을 수행하기 위한 작업을 분리한다.
  2. 처음에는 build & deb룰을 수행하고, 이 결과는 devel 레포지토리에 올라간다.
  3. 개발및 테스트 과정에서는 devel에 있는 패키지를 install 해서 테스트를 진행한다.
  4. 테스트가 끝나면 release작업을 수행한다. release 작업의 경우 새로 빌드 할 필요 없이 가장 최신의 deb 파일을 stable로 복사하면 된다.

chef와의 통합

dev는 192.168.56.5/devel, stable는 192.168.56.5/stable 를 레포지토리로 등록해야 한다. 인스턴스들은 자신이 어느 단계(dev, op)에 있는지 알수 있어야 한다. /etc/hostname으로 위치를 알 수 있도록 설정했다. 즉 dev.webserver.joinc.priv, op.webserver.joinc.priv와 같이 호스트 이름을 설정했다. 인스턴스가 chef client를 실행하면 호스트이름으로 부터 자신의 단계를 읽어오고, 이 정보를 기준으로 레포지토리 정보를 설정하게 했다. 이 과정을 완전히 이해하려면 Chef를 알아야 한다. 시간이 되면 chef는 따로 다루도록 하겠다.