Ubuntu18.04にDockerとNginxを使用してGoWebアプリケーションをデプロイする方法
著者は、 Write for DOnations プログラムの一環として、 Free and Open SourceFundを選択して寄付を受け取りました。
序章
Docker は、今日使用されている最も一般的なコンテナ化ソフトウェアです。 これにより、開発者はアプリを環境と一緒に簡単にパッケージ化できるため、実行ごとに同じ目的の環境を提供しながら、反復サイクルを短縮し、リソース効率を向上させることができます。 Docker Compose は、最新のアプリ要件を容易にするコンテナーオーケストレーションツールです。 これにより、相互接続された複数のコンテナーを同時に実行できます。 コンテナーを手動で実行する代わりに、オーケストレーションツールを使用すると、開発者はコンテナーを同時に制御、スケーリング、および拡張できます。
NginxをフロントエンドWebサーバーとして使用する利点は、そのパフォーマンス、構成可能性、およびTLSターミネーションであり、アプリがこれらのタスクを完了する必要がなくなります。 nginx-proxy は、Dockerコンテナー用の自動システムであり、リバースプロキシとして機能するようにNginxを構成するプロセスを大幅に簡素化します。 そのLet’sEncrypt アドオンは、nginx-proxy
に付随して、プロキシされたコンテナの証明書の生成と更新を自動化できます。
このチュートリアルでは、 gorilla / mux をリクエストルーターとして、NginxをWebサーバーとして、すべてDockerコンテナー内に配置し、DockerComposeによって調整されたGoWebアプリケーションの例をデプロイします。 Let’sEncryptアドオンをリバースプロキシとしてnginx-proxy
を使用します。 このチュートリアルの最後に、Dockerを使用し、Let’s Encrypt証明書で保護された、複数のルートを使用してドメインでアクセス可能なGoWebアプリをデプロイします。
前提条件
- ルート権限を持つUbuntu18.04サーバー、およびセカンダリの非ルートアカウント。 これは、この初期サーバーセットアップガイドに従ってセットアップできます。 このチュートリアルでは、root以外のユーザーは
sammy
です。 - Ubuntu18.04にDockerをインストールする方法の最初の2つの手順に従ってDockerをインストールします。
- Ubuntu18.04にDockerComposeをインストールする方法の最初の手順に従ってインストールされたDockerCompose。
- 完全に登録されたドメイン名。 このチュートリアルでは、全体を通して
your_domain
を使用します。 Freenom で無料で入手するか、選択したドメインレジストラを使用できます。 - サーバーのパブリックIPアドレスを指す
your_domain
を含むDNS「A」レコード。 それらを追加する方法の詳細については、この紹介に従ってDigitalOceanDNSを参照できます。 - Dockerとそのアーキテクチャーの理解。 Dockerの概要については、 Dockerエコシステム:一般的なコンポーネントの概要を参照してください。
ステップ1—サンプルのGoWebアプリを作成する
このステップでは、ワークスペースを設定し、後でコンテナ化する単純なGoWebアプリを作成します。 Goアプリは、柔軟性と速度で選択された強力な gorilla /muxリクエストルーターを使用します。
このチュートリアルでは、すべてのデータを~/go-docker
の下に保存します。 これを行うには、次のコマンドを実行します。
- mkdir ~/go-docker
そこに移動します:
- cd ~/go-docker
サンプルのGoWebアプリをmain.go
という名前のファイルに保存します。 テキストエディタを使用して作成します。
- nano main.go
次の行を追加します。
package main
import (
"fmt"
"net/http"
"github.com/gorilla/mux"
)
func main() {
r := mux.NewRouter()
r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "<h1>This is the homepage. Try /hello and /hello/Sammy\n</h1>")
})
r.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "<h1>Hello from Docker!\n</h1>")
})
r.HandleFunc("/hello/{name}", func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
title := vars["name"]
fmt.Fprintf(w, "<h1>Hello, %s!\n</h1>", title)
})
http.ListenAndServe(":80", r)
}
まず、net/http
およびgorilla/mux
パッケージをインポートします。これらのパッケージは、HTTPサーバーの機能とルーティングを提供します。
gorilla/mux
パッケージは、より簡単で強力なリクエストルーターとディスパッチャーを実装すると同時に、標準ルーターとのインターフェイスの互換性を維持します。 ここでは、新しいmux
ルーターをインスタンス化し、変数r
に格納します。 次に、/
、/hello
、および/hello/{name}
の3つのルートを定義します。 最初の(/
)はホームページとして機能し、ページへのメッセージを含めます。 2番目(/hello
)は、訪問者に挨拶を返します。 3番目のルート(/hello/{name}
)では、パラメーターとして名前を取り、名前が挿入されたグリーティングメッセージを表示するように指定します。
ファイルの最後で、http.ListenAndServe
でHTTPサーバーを起動し、構成したルーターを使用して、ポート80
でリッスンするように指示します。
ファイルを保存して閉じます。
Goアプリを実行する前に、まずコンパイルして、Dockerコンテナー内で実行できるようにパックする必要があります。 Goはコンパイル言語であるため、プログラムを実行する前に、コンパイラはプログラミングコードを実行可能マシンコードに変換します。
ワークスペースを設定し、サンプルのGoWebアプリを作成しました。 次に、自動化されたLet’sEncrypt証明書のプロビジョニングを使用してnginx-proxy
を展開します。
ステップ2—Let’sEncryptを使用してnginx-proxyをデプロイする
HTTPSでアプリを保護することが重要です。 これを実現するには、DockerComposeを介してnginx-proxy
を、Let’sEncryptアドオンとともにデプロイします。 これにより、nginx-proxy
を使用してプロキシされたDockerコンテナーが保護され、TLS証明書の作成と更新が自動的に処理されるため、HTTPSを介してアプリが保護されます。
nginx-proxy
のDockerCompose構成をnginx-proxy-compose.yaml
という名前のファイルに保存します。 次のコマンドを実行して作成します。
- nano nginx-proxy-compose.yaml
次の行をファイルに追加します。
version: '2'
services:
nginx-proxy:
restart: always
image: jwilder/nginx-proxy
ports:
- "80:80"
- "443:443"
volumes:
- "/etc/nginx/vhost.d"
- "/usr/share/nginx/html"
- "/var/run/docker.sock:/tmp/docker.sock:ro"
- "/etc/nginx/certs"
letsencrypt-nginx-proxy-companion:
restart: always
image: jrcs/letsencrypt-nginx-proxy-companion
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
volumes_from:
- "nginx-proxy"
ここでは、2つのコンテナーを定義しています。1つはnginx-proxy
用で、もう1つはLet’s Encryptアドオン(letsencrypt-nginx-proxy-companion
)用です。 プロキシの場合、イメージjwilder/nginx-proxy
を指定し、HTTPポートとHTTPSポートを公開してマッピングし、最後にNginx関連データを永続化するためにコンテナーにアクセスできるボリュームを定義します。
2番目のブロックでは、Let’sEncryptアドオン構成のイメージに名前を付けます。 次に、ボリュームを定義し、次に継承するプロキシコンテナーから既存のボリュームを定義することにより、Dockerのソケットへのアクセスを構成します。 両方のコンテナーのrestart
プロパティはalways
に設定されており、Dockerに常にコンテナーを維持するように指示します(クラッシュまたはシステムの再起動の場合)。
ファイルを保存して閉じます。
次のコマンドを実行して、nginx-proxy
をデプロイします。
- docker-compose -f nginx-proxy-compose.yaml up -d
Docker Composeは、-f
フラグを介してカスタムの名前付きファイルを受け入れます。 up
コマンドはコンテナーを実行し、-d
フラグ(デタッチモード)は、コンテナーをバックグラウンドで実行するように指示します。
最終的な出力は次のようになります。
OutputCreating network "go-docker_default" with the default driver
Pulling nginx-proxy (jwilder/nginx-proxy:)...
latest: Pulling from jwilder/nginx-proxy
a5a6f2f73cd8: Pull complete
2343eb083a4e: Pull complete
...
Digest: sha256:619f390f49c62ece1f21dfa162fa5748e6ada15742e034fb86127e6f443b40bd
Status: Downloaded newer image for jwilder/nginx-proxy:latest
Pulling letsencrypt-nginx-proxy-companion (jrcs/letsencrypt-nginx-proxy-companion:)...
latest: Pulling from jrcs/letsencrypt-nginx-proxy-companion
...
Creating go-docker_nginx-proxy_1 ... done
Creating go-docker_letsencrypt-nginx-proxy-companion_1 ... done
DockerComposeを使用してnginx-proxy
とそのLet’sEncryptコンパニオンをデプロイしました。 次に、GoWebアプリ用のDockerfileを作成します。
ステップ3—GoWebアプリをDocker化する
このセクションでは、DockerがGoWebアプリの不変イメージを作成する方法に関する手順を含むDockerfileを作成します。 Dockerは、Dockerfileにある命令を使用して、コンテナーのスナップショットに似た不変のアプリイメージを構築します。 イメージの不変性は、特定のイメージに基づいてコンテナーが実行されるたびに同じ環境を保証します。
テキストエディタでDockerfile
を作成します。
- nano Dockerfile
次の行を追加します。
FROM golang:alpine AS build
RUN apk --no-cache add gcc g++ make git
WORKDIR /go/src/app
COPY . .
RUN go mod init webserver
RUN go mod tidy
RUN GOOS=linux go build -ldflags="-s -w" -o ./bin/web-app ./main.go
FROM alpine:3.13
RUN apk --no-cache add ca-certificates
WORKDIR /usr/bin
COPY --from=build /go/src/app/bin /go/bin
EXPOSE 80
ENTRYPOINT /go/bin/web-app --port 80
このDockerfileには2つの段階があります。 最初のステージでは、golang:alpine
ベースを使用します。このベースには、AlpineLinuxにプリインストールされたGoが含まれています。
次に、Goアプリに必要なコンパイルツールとして、gcc
、g++
、make
、およびgit
をインストールします。 作業ディレクトリを/go/src/app
に設定します。これは、デフォルトのGOPATHの下にあります。 また、現在のディレクトリの内容をコンテナにコピーします。 最初の段階は、コードから使用されるパッケージを再帰的にフェッチし、シンボルとデバッグ情報なしでリリースするためにmain.go
ファイルをコンパイルすることで終了します(-ldflags="-s -w"
を渡すことによって)。 Goプログラムをコンパイルすると、デバッグに使用されるバイナリの別の部分が保持されますが、この追加情報はメモリを使用するため、実稼働環境にデプロイするときに保持する必要はありません。
第2段階は、alpine:3.13
(Alpine Linux 3.13)に基づいています。 信頼できるCA証明書をインストールし、コンパイルされたアプリバイナリを最初のステージから現在のイメージにコピーし、ポート80
を公開し、アプリバイナリをイメージエントリポイントとして設定します。
ファイルを保存して閉じます。
Goアプリ用のDockerfileを作成しました。このファイルは、パッケージをフェッチし、リリース用にコンパイルして、コンテナーの作成時に実行します。 次のステップでは、Docker Compose yaml
ファイルを作成し、Dockerで実行してアプリをテストします。
ステップ4—Docker作成ファイルの作成と実行
次に、Docker Compose構成ファイルを作成し、前の手順で作成したDockerイメージを実行するために必要な構成を記述します。 次に、それを実行して、正しく機能するかどうかを確認します。 一般に、Docker Compose構成ファイルは、アプリが必要とするコンテナー、それらの設定、ネットワーク、およびボリュームを指定します。 これらの要素を同時に開始および停止できるように指定することもできます。
GoWebアプリのDockerCompose構成をgo-app-compose.yaml
という名前のファイルに保存します。 次のコマンドを実行して作成します。
- nano go-app-compose.yaml
このファイルに次の行を追加します。
version: '2'
services:
go-web-app:
restart: always
build:
dockerfile: Dockerfile
context: .
environment:
- VIRTUAL_HOST=your_domain
- LETSENCRYPT_HOST=your_domain
your_domain
を両方ともドメイン名に置き換えることを忘れないでください。 ファイルを保存して閉じます。
このDockerCompose構成には、Go Webアプリとなる1つのコンテナー(go-web-app
)が含まれています。 前の手順で作成したDockerfileを使用してアプリをビルドし、ビルドのコンテキストとしてソースコードを含む現在のディレクトリを取得します。 さらに、VIRTUAL_HOST
とLETSENCRYPT_HOST
の2つの環境変数を設定します。 nginx-proxy
は、VIRTUAL_HOST
を使用して、要求を受け入れるドメインを認識します。 LETSENCRYPT_HOST
は、TLS証明書を生成するためのドメイン名を指定します。ワイルドカードドメインを指定しない限り、VIRTUAL_HOST
と同じである必要があります。
次に、次のコマンドを使用して、DockerComposeを介してGoWebアプリをバックグラウンドで実行します。
- docker-compose -f go-app-compose.yaml up -d
最終的な出力は次のようになります。
OutputCreating network "go-docker_default" with the default driver
Building go-web-app
Step 1/12 : FROM golang:alpine AS build
---> b97a72b8e97d
...
Successfully tagged go-docker_go-web-app:latest
WARNING: Image for service go-web-app was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`.
Creating go-docker_go-web-app_1 ... done
コマンドの実行後に表示される出力を確認すると、DockerはDockerfileの構成に従ってアプリイメージを構築するすべてのステップをログに記録しました。
これで、https://your_domain/
に移動してホームページを表示できます。 Webアプリのホームアドレスに、最初のステップで定義した/
ルートの結果としてページが表示されます。
次に、https://your_domain/hello
に移動します。 手順1の/hello
ルートのコードで定義したメッセージが表示されます。
最後に、https://your_domain/hello/Sammy
のように、Webアプリのアドレスに名前を追加して、他のルートをテストしてみてください。
注:無効なTLS証明書に関するエラーを受け取った場合は、Let’sEncryptアドオンが証明書をプロビジョニングするまで数分待ちます。 しばらくしてもエラーが発生する場合は、入力した内容をこの手順に示されているコマンドと構成と照らし合わせて再確認してください。
Docker Composeファイルを作成し、コンテナー内でGoアプリを実行するための構成を記述しました。 最後に、ドメインに移動して、gorilla/mux
ルーターのセットアップがDockerizedGoWebアプリへのリクエストを正しく処理していることを確認しました。
結論
これで、Ubuntu18.04にDockerとNginxを使用してGoWebアプリを正常にデプロイできました。 Dockerを使用すると、アプリが実行される環境が実行されるたびに同じであることが保証されるため、アプリケーションの保守が簡単になります。 gorilla / mux パッケージには優れたドキュメントがあり、ルートの命名や静的ファイルの提供など、より高度な機能を提供します。 カスタムタイムアウトの定義など、Go HTTPサーバーモジュールをさらに制御するには、公式ドキュメントにアクセスしてください。