GitHub 웹훅을 사용하여 자동으로 웹 애플리케이션 배포
게시 됨: 2022-03-11웹 응용 프로그램을 개발하고 관리되지 않는 자체 서버에서 실행하려는 사람은 응용 프로그램 배포 및 향후 업데이트 푸시와 관련된 지루한 프로세스를 알고 있습니다. PaaS(Platform as a Service) 제공업체는 개별 서버를 프로비저닝하고 구성하는 과정을 거치지 않고도 웹 애플리케이션을 쉽게 배포할 수 있도록 하여 비용을 약간 증가시키고 유연성을 감소시켰습니다. PaaS를 사용하면 작업이 더 쉬워졌을 수 있지만 때때로 우리는 여전히 자체 관리되지 않는 서버에 애플리케이션을 배포해야 하거나 배포하기를 원합니다. 웹 응용 프로그램을 서버에 배포하는 이 프로세스를 자동화하는 것은 처음에는 압도적으로 들릴 수 있지만 실제로는 이를 자동화하는 간단한 도구를 찾는 것이 생각보다 쉬울 수 있습니다. 이 도구를 구현하는 것이 얼마나 쉬운지는 귀하의 요구 사항이 얼마나 단순한지에 달려 있지만 확실히 달성하기 어렵지 않으며 웹 애플리케이션의 지루하고 반복적인 부분을 수행하여 많은 시간과 노력을 절약하는 데 도움이 될 수 있습니다. 배포.
많은 개발자가 웹 응용 프로그램의 배포 프로세스를 자동화하는 고유한 방법을 생각해 냈습니다. 웹 애플리케이션을 배포하는 방법은 사용 중인 정확한 기술 스택에 크게 좌우되기 때문에 이러한 자동화 솔루션은 서로 다릅니다. 예를 들어 PHP 웹 사이트를 자동으로 배포하는 단계는 Node.js 웹 애플리케이션을 배포하는 것과 다릅니다. Dokku와 같은 매우 일반적인 솔루션이 존재하며 이러한 솔루션(빌드팩이라고 함)은 더 넓은 범위의 기술 스택과 잘 작동합니다.
이 자습서에서는 GitHub 웹훅, 빌드팩 및 Procfiles를 사용하여 웹 애플리케이션 배포를 자동화하기 위해 구축할 수 있는 간단한 도구 이면의 기본 아이디어를 살펴봅니다. 이 기사에서 탐색할 프로토타입 프로그램의 소스 코드는 GitHub에서 사용할 수 있습니다.
웹 애플리케이션 시작하기
웹 애플리케이션 배포를 자동화하기 위해 간단한 Go 프로그램을 작성합니다. Go에 익숙하지 않은 경우 이 기사 전체에서 사용되는 코드 구성이 상당히 간단하고 이해하기 쉬워야 하므로 주저하지 말고 따라하십시오. 원한다면 전체 프로그램을 원하는 언어로 아주 쉽게 이식할 수 있습니다.
시작하기 전에 시스템에 Go 배포판이 설치되어 있는지 확인하십시오. Go를 설치하려면 공식 문서에 설명된 단계를 따르세요.
다음으로 GitHub 리포지토리를 복제하여 이 도구의 소스 코드를 다운로드할 수 있습니다. 이렇게 하면 이 문서의 코드 조각에 해당 파일 이름 레이블이 지정되어 있으므로 쉽게 따라할 수 있습니다. 원한다면 바로 사용해 볼 수 있습니다.
이 프로그램에 Go를 사용하는 주요 이점 중 하나는 외부 종속성을 최소화하는 방식으로 빌드할 수 있다는 것입니다. 우리의 경우 서버에서 이 프로그램을 실행하려면 Git과 Bash가 설치되어 있는지 확인하기만 하면 됩니다. Go 프로그램은 정적으로 연결된 바이너리로 컴파일되므로 컴퓨터에서 프로그램을 컴파일하고 서버에 업로드하고 거의 노력 없이 실행할 수 있습니다. 오늘날 대부분의 다른 인기 있는 언어의 경우 배포 자동화기를 실행하기 위해 서버에 설치된 일부 거대한 런타임 환경 또는 인터프리터가 필요합니다. Go 프로그램은 올바르게 수행되면 CPU 및 RAM에서 매우 쉽게 이동할 수 있습니다. 이는 이와 같은 프로그램에서 원하는 것입니다.
GitHub 웹훅
GitHub Webhook을 사용하면 리포지토리 내에서 무언가가 변경되거나 일부 사용자가 호스팅된 리포지토리에서 특정 작업을 수행할 때마다 이벤트를 내보내도록 GitHub 리포지토리를 구성할 수 있습니다. 이를 통해 사용자는 이러한 이벤트를 구독하고 저장소 주변에서 발생하는 다양한 이벤트의 URL 호출을 통해 알림을 받을 수 있습니다.
웹훅을 만드는 것은 매우 간단합니다.
- 저장소의 설정 페이지로 이동합니다.
- 왼쪽 탐색 메뉴에서 "Webhooks & Services"를 클릭합니다.
- "웹훅 추가" 버튼을 클릭합니다.
- URL을 설정하고 선택적으로 비밀(수신자가 페이로드를 확인할 수 있음)
- 필요에 따라 양식에서 다른 선택을 하십시오.
- 녹색 "웹훅 추가" 버튼을 클릭하여 양식을 제출합니다.
GitHub는 Webhook이 정확히 어떻게 작동하는지, 다양한 이벤트에 대한 응답으로 페이로드에 어떤 정보가 전달되는지 등에 대한 광범위한 문서를 제공합니다. 이 기사의 목적을 위해 우리는 특히 누군가가 웹훅이 작동할 때마다 발생하는 "푸시" 이벤트에 관심이 있습니다. 모든 저장소 분기로 푸시합니다.
빌드팩
빌드팩은 요즘 거의 표준입니다. 많은 PaaS 제공업체에서 사용하는 빌드팩을 사용하면 애플리케이션을 배포하기 전에 스택을 구성하는 방법을 지정할 수 있습니다. 웹 애플리케이션용 빌드팩을 작성하는 것은 정말 쉽지만 웹에서 빠른 검색을 통해 수정 없이 웹 애플리케이션에 사용할 수 있는 빌드팩을 찾는 경우가 많습니다.
Heroku와 같은 PaaS에 애플리케이션을 배포했다면 빌드팩이 무엇인지, 어디서 찾을 수 있는지 이미 알고 있을 것입니다. Heroku에는 빌드팩의 구조에 대한 포괄적인 문서와 잘 구축된 인기 있는 빌드팩 목록이 있습니다.
자동화 프로그램은 컴파일 스크립트를 사용하여 응용 프로그램을 실행하기 전에 준비합니다. 예를 들어 Heroku의 Node.js 빌드는 package.json 파일을 구문 분석하고 적절한 버전의 Node.js를 다운로드하며 애플리케이션에 대한 NPM 종속성을 다운로드합니다. 일을 단순하게 유지하기 위해 프로토타입 프로그램에서 빌드팩을 광범위하게 지원하지 않을 것이라는 점은 주목할 가치가 있습니다. 지금은 빌드팩 스크립트가 Bash와 함께 실행되도록 작성되었으며 새로운 Ubuntu 설치에서 그대로 실행된다고 가정합니다. 필요한 경우 앞으로 더 난해한 요구 사항을 해결하기 위해 이를 쉽게 확장할 수 있습니다.
프로필
Procfile은 애플리케이션에 있는 다양한 유형의 프로세스를 정의할 수 있는 간단한 텍스트 파일입니다. 대부분의 간단한 애플리케이션의 경우 HTTP 요청을 처리하는 프로세스인 단일 "웹" 프로세스가 이상적으로 있을 것입니다.
프로필 작성은 쉽습니다. 이름, 콜론, 프로세스를 생성할 명령을 차례로 입력하여 한 줄에 하나의 프로세스 유형을 정의합니다.
<type>: <command>
예를 들어 Node.js 기반 웹 애플리케이션으로 작업하는 경우 웹 서버를 시작하려면 "node index.js" 명령을 실행합니다. 코드의 기본 디렉터리에 Procfile을 만들고 다음과 같이 "Procfile"로 이름을 지정할 수 있습니다.
web: node index.js
코드를 가져온 후 자동으로 시작할 수 있도록 응용 프로그램에서 Procfiles에서 프로세스 유형을 정의해야 합니다.
이벤트 처리
프로그램 내에 GitHub에서 들어오는 POST 요청을 수신할 수 있는 HTTP 서버를 포함해야 합니다. GitHub의 이러한 요청을 처리하기 위해 전용 URL 경로가 필요합니다. 이러한 수신 페이로드를 처리하는 함수는 다음과 같습니다.
// hook.go type HookOptions struct { App *App Secret string } func NewHookHandler(o *HookOptions) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { evName := r.Header.Get("X-Github-Event") if evName != "push" { log.Printf("Ignoring '%s' event", evName) return } body, err := ioutil.ReadAll(r.Body) if err != nil { http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } if o.Secret != "" { ok := false for _, sig := range strings.Fields(r.Header.Get("X-Hub-Signature")) { if !strings.HasPrefix(sig, "sha1=") { continue } sig = strings.TrimPrefix(sig, "sha1=") mac := hmac.New(sha1.New, []byte(o.Secret)) mac.Write(body) if sig == hex.EncodeToString(mac.Sum(nil)) { ok = true break } } if !ok { log.Printf("Ignoring '%s' event with incorrect signature", evName) return } } ev := github.PushEvent{} err = json.Unmarshal(body, &ev) if err != nil { log.Printf("Ignoring '%s' event with invalid payload", evName) http.Error(w, "Bad Request", http.StatusBadRequest) return } if ev.Repo.FullName == nil || *ev.Repo.FullName != o.App.Repo { log.Printf("Ignoring '%s' event with incorrect repository name", evName) http.Error(w, "Bad Request", http.StatusBadRequest) return } log.Printf("Handling '%s' event for %s", evName, o.App.Repo) err = o.App.Update() if err != nil { return } }) }
이 페이로드를 생성한 이벤트 유형을 확인하는 것으로 시작합니다. "푸시" 이벤트에만 관심이 있으므로 다른 모든 이벤트는 무시할 수 있습니다. "푸시" 이벤트만 내보내도록 웹후크를 구성하더라도 후크 끝점에서 수신할 것으로 예상할 수 있는 다른 종류의 이벤트인 "ping"이 적어도 하나는 있을 것입니다. 이 이벤트의 목적은 웹훅이 GitHub에서 성공적으로 구성되었는지 확인하는 것입니다.
다음으로, 들어오는 요청의 전체 본문을 읽고, 웹훅을 구성하는 데 사용할 동일한 비밀을 사용하여 HMAC-SHA1을 계산하고, 웹훅의 헤더에 포함된 서명과 비교하여 들어오는 페이로드의 유효성을 결정합니다. 요구. 우리 프로그램에서는 비밀이 구성되지 않은 경우 이 유효성 검사 단계를 무시합니다. 참고로 여기에서 처리하려는 데이터의 양에 대한 상한선이 없는 상태에서 전체 본문을 읽는 것은 현명한 생각이 아닐 수 있지만 중요한 측면에 초점을 맞추기 위해 간단하게 유지하겠습니다. 이 도구의.
그런 다음 Go용 GitHub 클라이언트 라이브러리의 구조체를 사용하여 수신 페이로드를 비정렬화합니다. "푸시" 이벤트라는 것을 알고 있으므로 PushEvent 구조체를 사용할 수 있습니다. 그런 다음 표준 json 인코딩 라이브러리를 사용하여 페이로드를 구조체의 인스턴스로 비정렬화합니다. 몇 가지 온전성 검사를 수행하고 모든 것이 정상이면 애플리케이션 업데이트를 시작하는 함수를 호출합니다.
애플리케이션 업데이트
웹훅 엔드포인트에서 이벤트 알림을 받으면 애플리케이션 업데이트를 시작할 수 있습니다. 이 기사에서 우리는 이 메커니즘의 상당히 간단한 구현을 살펴보고 확실히 개선의 여지가 있을 것입니다. 그러나 몇 가지 기본적인 자동화 배포 프로세스를 시작하는 데 도움이 될 것입니다.
로컬 저장소 초기화
이 프로세스는 애플리케이션 배포를 처음 시도하는 것인지 확인하기 위한 간단한 검사로 시작됩니다. 로컬 리포지토리 디렉토리가 존재하는지 확인함으로써 그렇게 할 것입니다. 존재하지 않는 경우 먼저 로컬 저장소를 초기화합니다.
// app.go func (a *App) initRepo() error { log.Print("Initializing repository") err := os.MkdirAll(a.repoDir, 0755) // Check err cmd := exec.Command("git", "--git-dir="+a.repoDir, "init") cmd.Stderr = os.Stderr err = cmd.Run() // Check err cmd = exec.Command("git", "--git-dir="+a.repoDir, "remote", "add", "origin", fmt.Sprintf("[email protected]:%s.git", a.Repo)) cmd.Stderr = os.Stderr err = cmd.Run() // Check err return nil }
App 구조체의 이 메서드는 로컬 저장소를 초기화하는 데 사용할 수 있으며 메커니즘은 매우 간단합니다.
- 존재하지 않는 경우 로컬 리포지토리에 대한 디렉터리를 만듭니다.
- "git init" 명령을 사용하여 베어 리포지토리를 만듭니다.
- 원격 리포지토리의 URL을 로컬 리포지토리에 추가하고 이름을 "origin"으로 지정합니다.
초기화된 저장소가 있으면 변경 사항을 가져오는 것이 간단해야 합니다.
변경사항 가져오기
원격 저장소에서 변경 사항을 가져오려면 하나의 명령만 호출하면 됩니다.
// app.go func (a *App) fetchChanges() error { log.Print("Fetching changes") cmd := exec.Command("git", "--git-dir="+a.repoDir, "fetch", "-f", "origin", "master:master") cmd.Stderr = os.Stderr return cmd.Run() }
이러한 방식으로 로컬 리포지토리에 대해 "git fetch"를 수행하면 Git이 특정 시나리오에서 빨리 감기를 할 수 없는 문제를 피할 수 있습니다. 강제 가져오기가 의존해야 하는 것은 아니지만 원격 저장소에 강제 푸시를 수행해야 하는 경우에는 정상적으로 처리됩니다.
애플리케이션 컴파일
빌드팩의 스크립트를 사용하여 배포 중인 애플리케이션을 컴파일하기 때문에 여기에서의 작업은 비교적 쉽습니다.
// app.go func (a *App) compileApp() error { log.Print("Compiling application") _, err := os.Stat(a.appDir) if !os.IsNotExist(err) { err = os.RemoveAll(a.appDir) // Check err } err = os.MkdirAll(a.appDir, 0755) // Check err cmd := exec.Command("git", "--git-dir="+a.repoDir, "--work-tree="+a.appDir, "checkout", "-f", "master") cmd.Dir = a.appDir cmd.Stderr = os.Stderr err = cmd.Run() // Check err buildpackDir, err := filepath.Abs("buildpack") // Check err cmd = exec.Command("bash", filepath.Join(buildpackDir, "bin", "detect"), a.appDir) cmd.Dir = buildpackDir cmd.Stderr = os.Stderr err = cmd.Run() // Check err cacheDir, err := filepath.Abs("cache") // Check err err = os.MkdirAll(cacheDir, 0755) // Check err cmd = exec.Command("bash", filepath.Join(buildpackDir, "bin", "compile"), a.appDir, cacheDir) cmd.Dir = a.appDir cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr return cmd.Run() }
이전 애플리케이션 디렉토리(있는 경우)를 제거하는 것으로 시작합니다. 다음으로, 우리는 새로운 것을 만들고 그것에 마스터 브랜치의 내용을 체크아웃합니다. 그런 다음 구성된 빌드팩에서 "감지" 스크립트를 사용하여 애플리케이션이 처리할 수 있는 것인지 확인합니다. 그런 다음 필요한 경우 빌드팩 컴파일 프로세스를 위한 "캐시" 디렉토리를 만듭니다. 이 디렉토리는 빌드 전반에 걸쳐 지속되므로 이전 컴파일 프로세스에서 이미 디렉토리가 존재하기 때문에 새 디렉토리를 생성할 필요가 없을 수도 있습니다. 이 시점에서 빌드팩에서 "컴파일" 스크립트를 호출하고 시작하기 전에 애플리케이션에 필요한 모든 것을 준비하도록 할 수 있습니다. 빌드팩이 제대로 실행되면 이전에 캐시된 리소스의 캐싱 및 재사용을 자체적으로 처리할 수 있습니다.

응용 프로그램 다시 시작
이 자동화된 배포 프로세스를 구현할 때 컴파일 프로세스를 시작하기 전에 이전 프로세스를 중지하고 컴파일 단계가 완료되면 새 프로세스를 시작합니다. 이렇게 하면 도구를 쉽게 구현할 수 있지만 자동화된 배포 프로세스를 개선하는 몇 가지 잠재적으로 놀라운 방법이 남습니다. 이 프로토타입을 개선하려면 업데이트 중 다운타임이 없는지 확인하는 것으로 시작할 수 있습니다. 지금은 더 간단한 접근 방식을 계속할 것입니다.
// app.go func (a *App) stopProcs() error { log.Print(".. stopping processes") for _, n := range a.nodes { err := n.Stop() if err != nil { return err } } return nil } func (a *App) startProcs() error { log.Print("Starting processes") err := a.readProcfile() if err != nil { return err } for _, n := range a.nodes { err = n.Start() if err != nil { return err } } return nil }
프로토타입에서는 노드 배열을 반복하여 다양한 프로세스를 중지하고 시작합니다. 여기서 각 노드는 애플리케이션 인스턴스 중 하나에 해당하는 프로세스입니다(서버에서 이 도구를 실행하기 전에 구성된 대로). 도구 내에서 각 노드에 대한 프로세스의 현재 상태를 추적합니다. 또한 개별 로그 파일을 유지 관리합니다. 모든 노드가 시작되기 전에 각 노드에는 지정된 포트 번호에서 시작하는 고유한 포트가 할당됩니다.
// node.go func NewNode(app *App, name string, no int, port int) (*Node, error) { logFile, err := os.OpenFile(filepath.Join(app.logsDir, fmt.Sprintf("%s.%d.txt", name, no)), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) if err != nil { return nil, err } n := &Node{ App: app, Name: name, No: no, Port: port, stateCh: make(chan NextState), logFile: logFile, } go func() { for { next := <-n.stateCh if n.State == next.State { if next.doneCh != nil { close(next.doneCh) } continue } switch next.State { case StateUp: log.Printf("Starting process %s.%d", n.Name, n.No) cmd := exec.Command("bash", "-c", "for f in .profile.d/*; do source $f; done; "+n.Cmd) cmd.Env = append(cmd.Env, fmt.Sprintf("HOME=%s", n.App.appDir)) cmd.Env = append(cmd.Env, fmt.Sprintf("PORT=%d", n.Port)) cmd.Env = append(cmd.Env, n.App.Env...) cmd.Dir = n.App.appDir cmd.Stdout = n.logFile cmd.Stderr = n.logFile err := cmd.Start() if err != nil { log.Printf("Process %s.%d exited", n.Name, n.No) n.State = StateUp } else { n.Process = cmd.Process n.State = StateUp } if next.doneCh != nil { close(next.doneCh) } go func() { err := cmd.Wait() if err != nil { log.Printf("Process %s.%d exited", n.Name, n.No) n.stateCh <- NextState{ State: StateDown, } } }() case StateDown: log.Printf("Stopping process %s.%d", n.Name, n.No) if n.Process != nil { n.Process.Kill() n.Process = nil } n.State = StateDown if next.doneCh != nil { close(next.doneCh) } } } }() return n, nil } func (n *Node) Start() error { n.stateCh <- NextState{ State: StateUp, } return nil } func (n *Node) Stop() error { doneCh := make(chan int) n.stateCh <- NextState{ State: StateDown, doneCh: doneCh, } <-doneCh return nil }
얼핏 보면 이것은 우리가 지금까지 했던 것보다 조금 더 복잡해 보일 수 있습니다. 이해하기 쉽도록 위의 코드를 네 부분으로 나누겠습니다. 처음 두 개는 "NewNode" 기능 내에 있습니다. 호출되면 "Node" 구조체의 인스턴스를 채우고 이 Node에 해당하는 프로세스를 시작 및 중지하는 데 도움이 되는 Go 루틴을 생성합니다. 다른 두 가지는 "Node" 구조체의 두 가지 메서드인 "Start"와 "Stop"입니다. 이 노드별 Go 루틴이 감시하는 특정 채널을 통해 "메시지"를 전달하여 프로세스를 시작하거나 중지합니다. 프로세스를 시작하기 위해 메시지를 전달하거나 프로세스를 중지하기 위해 다른 메시지를 전달할 수 있습니다. 프로세스 시작 또는 중지와 관련된 실제 단계는 단일 Go 루틴에서 발생하므로 경쟁 조건이 발생할 가능성이 없습니다.
Go 루틴은 "stateCh" 채널을 통해 "메시지"를 기다리는 무한 루프를 시작합니다. 이 채널에 전달된 메시지가 노드에 프로세스 시작을 요청하면("case StateUp" 내부) Bash를 사용하여 명령을 실행합니다. 그 동안 사용자 정의 환경 변수를 사용하도록 명령을 구성합니다. 또한 표준 출력 및 오류 스트림을 미리 정의된 로그 파일로 리디렉션합니다.
반면에 프로세스를 중지하려면("케이스 StateDown" 내부) 단순히 프로세스를 종료합니다. 여기서 창의력을 발휘할 수 있으며 프로세스를 종료하는 대신 즉시 SIGTERM을 보내고 실제로 종료하기 전에 몇 초 동안 기다리면 프로세스가 정상적으로 중지될 수 있습니다.
"시작" 및 "중지" 방법을 사용하면 적절한 메시지를 채널에 쉽게 전달할 수 있습니다. "Start" 메서드와 달리 "Stop" 메서드는 실제로 프로세스가 종료될 때까지 기다렸다가 반환합니다. "시작"은 단순히 메시지를 채널에 전달하여 프로세스를 시작하고 반환합니다.
모든 것을 결합
마지막으로 우리가 해야 할 일은 프로그램의 주요 기능 내에서 모든 것을 연결하는 것입니다. 여기에서 구성 파일을 로드 및 구문 분석하고, 빌드팩을 업데이트하고, 애플리케이션 업데이트를 한 번 시도하고, 웹 서버를 시작하여 GitHub에서 들어오는 "푸시" 이벤트 페이로드를 수신합니다.
// main.go func main() { cfg, err := toml.LoadFile("config.tml") catch(err) url, ok := cfg.Get("buildpack.url").(string) if !ok { log.Fatal("buildpack.url not defined") } err = UpdateBuildpack(url) catch(err) // Read configuration options into variables repo (string), env ([]string) and procs (map[string]int) // ... app, err := NewApp(repo, env, procs) catch(err) err = app.Update() catch(err) secret, _ := cfg.Get("hook.secret").(string) http.Handle("/hook", NewHookHandler(&HookOptions{ App: app, Secret: secret, })) addr, ok := cfg.Get("core.addr").(string) if !ok { log.Fatal("core.addr not defined") } err = http.ListenAndServe(addr, nil) catch(err) }
빌드팩이 단순한 Git 리포지토리여야 하기 때문에 "UpdateBuildpack"(buildpack.go에서 구현됨)은 빌드팩의 로컬 복사본을 업데이트하기 위해 리포지토리 URL과 함께 필요에 따라 "git clone" 및 "git pull"만 수행합니다.
그것을 밖으로 시도
아직 리포지토리를 복제하지 않은 경우 지금 할 수 있습니다. Go 배포판이 설치되어 있으면 프로그램을 즉시 컴파일할 수 있어야 합니다.
mkdir hopper cd hopper export GOPATH=`pwd` go get github.com/hjr265/toptal-hopper go install github.com/hjr265/toptal-hopper
이 명령 시퀀스는 hopper라는 디렉토리를 생성하고, 이를 GOPATH로 설정하고, 필요한 Go 라이브러리와 함께 GitHub에서 코드를 가져오고, "$GOPATH/bin" 디렉토리에서 찾을 수 있는 바이너리로 프로그램을 컴파일합니다. 이것을 서버에서 사용하기 전에 이것을 테스트할 간단한 웹 애플리케이션을 만들어야 합니다. 편의를 위해 Node.js와 같은 간단한 "Hello, world" 웹 애플리케이션을 만들고 이 테스트를 위해 포크하고 재사용할 수 있는 다른 GitHub 저장소에 업로드했습니다. 다음으로 컴파일된 바이너리를 서버에 업로드하고 동일한 디렉토리에 구성 파일을 생성해야 합니다.
# config.tml [core] addr = ":26590" [buildpack] url = "https://github.com/heroku/heroku-buildpack-nodejs.git" [app] repo = "hjr265/hopper-hello.js" [app.env] GREETING = "Hello" [app.procs] web = 1 [hook] secret = ""
구성 파일의 첫 번째 옵션인 "core.addr"을 사용하면 프로그램 내부 웹 서버의 HTTP 포트를 구성할 수 있습니다. 위의 예에서 ":26590"으로 설정하면 프로그램이 "http://{host}:26590/hook"에서 "push" 이벤트 페이로드를 수신 대기합니다. GitHub 웹훅을 설정할 때 "{host}"를 서버를 가리키는 도메인 이름 또는 IP 주소로 바꾸면 됩니다. 일종의 방화벽을 사용하는 경우에 대비하여 포트가 열려 있는지 확인하십시오.
다음으로 Git URL을 설정하여 빌드팩을 선택합니다. 여기서는 Heroku의 Node.js 빌드팩을 사용하고 있습니다.
"app"에서 "repo"를 애플리케이션 코드를 호스팅하는 GitHub 리포지토리의 전체 이름으로 설정합니다. "https://github.com/hjr265/hopper-hello.js"에서 예제 애플리케이션을 호스팅하고 있으므로 저장소의 전체 이름은 "hjr265/hopper-hello.js"입니다.
그런 다음 응용 프로그램에 대한 몇 가지 환경 변수와 필요한 각 유형의 프로세스 수를 설정합니다. 마지막으로 비밀을 선택하여 들어오는 "푸시" 이벤트 페이로드를 확인할 수 있습니다.
이제 서버에서 자동화 프로그램을 시작할 수 있습니다. 모든 것이 올바르게 구성된 경우(서버에서 저장소에 액세스할 수 있도록 SSH 키 배포 포함) 프로그램은 코드를 가져와 빌드팩을 사용하여 환경을 준비하고 애플리케이션을 시작해야 합니다. 이제 푸시 이벤트를 내보내고 "http://{host}:26590/hook"을 가리키도록 GitHub 리포지토리에 웹훅을 설정하기만 하면 됩니다. "{host}"를 서버를 가리키는 도메인 이름 또는 IP 주소로 바꿔야 합니다.
마지막으로 테스트하려면 예제 애플리케이션을 약간 변경하고 GitHub에 푸시하세요. 자동화 도구가 즉시 실행되고 서버의 리포지토리를 업데이트하고 애플리케이션을 컴파일한 다음 다시 시작한다는 것을 알 수 있습니다.
결론
대부분의 경험에서 이것이 매우 유용한 것임을 알 수 있습니다. 이 글에서 준비한 프로토타입 애플리케이션은 그대로 프로덕션 시스템에서 사용하고 싶은 것이 아닐 수 있습니다. 개선의 여지가 많습니다. 이와 같은 도구는 더 나은 오류 처리가 있어야 하고 정상적인 종료/다시 시작을 지원해야 하며 Docker와 같은 도구를 사용하여 프로세스를 직접 실행하는 대신 프로세스를 포함할 수 있습니다. 특정 사례에 정확히 무엇이 필요한지 파악하고 이를 위한 자동화 프로그램을 마련하는 것이 더 현명할 수 있습니다. 또는 인터넷 전체에서 사용할 수 있는 훨씬 더 안정적이고 오랜 시간 테스트를 거친 솔루션을 사용할 수도 있습니다. 그러나 매우 사용자 정의된 것을 출시하려는 경우 이 기사가 이를 수행하고 웹 애플리케이션 배포 프로세스를 자동화하여 장기적으로 얼마나 많은 시간과 노력을 절약할 수 있는지 보여주기를 바랍니다.