2019-12-04に投稿

MinQ開発日記 (3) Docker上にGo開発環境を構築

Goの導入

前回の続きです. まずGoを入れてファイアウォールの設定をして静的サイトを配信できるかチェックしてみようと思います.

ネコでもわかる!さくらのVPS講座第三回「Apacheをインストールしよう」第四回「phpとMariaDBをインストールしよう」の内容がベースになります.

Go言語を選んだ理由

本当はRustのRocketとかでやりたかった. のだがnightlyとか非同期とか色々と不安定な時期を完全に脱したとは言えない. 加えてRust自体が結構難しく(基本はそうでもないのだが)ベストプラクティスが不明である. それに比べるとGoは学習の困難が少ないように見える(最適化とかは知らん).

  1. メモリ管理不要
  2. 静的な型
  3. ビルトインWebサーバーとその容易な拡張性
  4. クラスと継承がない
  5. 文法が親しみやすい
  6. プログラミング全体を学ぶ教材が揃っている気がする
  7. クリーン・アーキテクチャとの相性が良さそう

1は大抵の動的な言語でも満たすので意味はないですが, 静的な型があるのは良い場合があります. クリーン・アーキテクチャは各層を依存性逆転で依存の方向が内側に向かうようにしています. これがPythonでは不可能ではないですが不自然さは否めません. Goはインターフェースがあるので特に悩まず実装できそうです.

ローカルなどの開発環境で動かせてもプロダクション環境では色々設定が必要になるのは面倒です. シンプルな三層アーキテクチャとしてもWebサーバー, APサーバー, DBサーバーが必要で, それ以外にアプリケーション・ロジックをWebフレームワークで作る必要があります. このうちWebサーバー, APサーバー, アプリケーション・ロジックがGoなら一纏めでできると思ったからです. 要はさっさとデプロイしたいということです.

Composition over Inheritance

らしく継承がないです. そもそもクラスないです. 構造体を組み合わせることになります. その他の文法もシンプルです. メソッド名の前にレシーバーを指定する記法は少し面食らいますが, selfを暗に陽に第一引数として取るよりは特別な引数感が出せます. なお同期処理なんかも簡単らしいです.

学習用言語としてはPythonが動く擬似コードなんて呼ばれることがありますが, Pythonの利用用途は数値計算とかその応用である機械学習などに集中しているように思います. 一方Goはランタイムがあるものの『Goならわかるシステムプログラミング』とか『Real World HTTP』, 『Goプログラミング実践入門』などいろんなことが基礎から学べます. 他にも『Writing A Compiler In Go』や『Writing A Interpreter In Go (Go言語でつくるインタプリタ) 』なんかもあり学習用言語としても優秀かなと思いました.

最後はMVCではなくクリーン・アーキテクチャを導入したいと思っているという話です. すでにこの話はしましたが, 型がある方が有利と思います.

PHPでない理由

PHPの良いところはHTMLとの親和性だと思う. テンプレートが必要な場合(動的レンダリングなど)では強みになると思う. Wordpressなんかが良い例だと思う. 一方Web APIのようにテンプレートが原則必要ない場合はこの優位点は消えると思う. MVCのようにサーバー側で色々やるからPHPの特色が生かせるのではないか. Vが切り離された状況なら他の言語でも良い気がしてならない. Laravelとか便利そうだがAPIサーバーを作るにはデカい. Lumenというのもあるが, そこまでしてPHPを使うべきなのか良くわからなくなってしまった.

Pythonでない理由

最終的にはクイズの成績をデータ分析とかしてみたいという野望がある. この点から言えばPythonを選択することは将来性があるとは思う. 文法的にもPythonの方が慣れているとは思う. Flaskのデコレータを使ったルートの定義は視認性が高いし, FastAPIというイベント駆動なWebフレームワークもある. ただデータ分析となるとバッチ処理になる気はする. 流行りのAIでクイズをレコメンドしたり最適な教材を生成したりとかなると意義はあるのかもしれないが, その元となるAPIがPythonである必要性はない気はする. よってクイズの成績とかを管理するWebアプリはPythonでやってみたい.

Rubyでない理由

Rubyの他のWebフレームワークは知らないのだが, RubyやるならやっぱりRoRがやりたいとも思うが, やはりデカすぎる気がする. 後昔gemが動かなかったトラウマからRubyに苦手意識がある.

Node.jsでない理由

JavaScriptはフロントだけで十分です(しかもこいつだけ言語じゃねぇ). APIサーバーを立てるわけですから, APIができたらJAMStackな構成でサイトを作ってみたいです.

Elixirでない理由

楽しそうではあるんですが, 同時にだるさもあります. サーバー(当然関数)自体を再帰的に呼び出すことで無限ループを作るらしいです.

Goの導入

Goバイナリのインストール

Goには公式のイメージがありますが, 前回は構築したCentOSベースのイメージに追加します. /usr/local/srcは自分でビルドするプログラムのソースをダウンロードするフォルダです. といってもビルドはしないので公式のダウンロード・ページからバイナリを取って来て/usr/local以下に展開するだけです.

ARG GO_VERSION=1.13.4
WORKDIR /usr/local/src
ADD https://dl.google.com/go/go${GO_VERSION}.linux-amd64.tar.gz ./
RUN tar -C /usr/local/ -xzf go${GO_VERSION}.linux-amd64.tar.gz && \
         rm /usr/local/src/*.tar.gz

Goのバージョンはbuild-argオプションで変更できるようにARG命令で定義しました. 2019/11/30の最新バージョンである1.13.4をデフォルト・バージョンとしています. またGolangのGOPATHやGOROOTについてに従って自作パッケージと外部パッケージのインストール・フォルダを分けます. なおADDでgoバイナリを取得していますが, wgetでもcurlでも同じです.

ENV PATH=$PATH:/usr/local/go/bin
ENV GOPATH=$HOME/go/package:$HOME/go/workspace
ENV PATH=$HOME/go/package/bin:$HOME/go/workspace/bin:$PATH

まずgo自体にパスを通す必要があります. GOPATHには二つのパスを指定しています. go getは最初に指定したパスにパッケージをインストールしてくれます. 上の例では以下のように分けました.

フォルダ名 用途
workspace 自作のGoプロジェクト
package サード・パーティ製のパッケージのインストール

それぞれのフォルダにはsrc, bin, そしてpkgというフォルダが必要です.

フォルダ名 用途
src ソースコードの保存
pkg パッケージをコンパイルした生成物
bin ビルド後の生成物

Goはパッケージという単位でソースをモジュール化します. それぞれがコンパイルされ, その後プロジェクトのmainパッケージを中心にリンクされ最終生成物として実行可能バイナリが出力されるようです. そのためbinフォルダにパスを設定しておきます. こうしておくとビルドされたコマンドを即呼び出せます.

Goのサンプルを以下のようにインストールして, 実行してみましょう. 折角なのでバージョンをコマンドラインから指定してみましょう.

docker build -t minq:0.1 --build-arg=1.13.4 context/
docker run -it --rm minq:0.1

後は対話モードで以下を実行します.

$ go get github.com/golang/example/hello
$ hello
Hello, Go examples!

リモート・パッケージも簡単にインストールでき, 実行できることがわかります. Dockerfileでは以下のようになります. workspaceとpackageフォルダそれぞれにsrc, bin, pkgを作成し, echoをインストールします.

RUN mkdir -p ${HOME}/go/workspace ${HOME}/go/workspace/src ${HOME}/go/workspace/pkg ${HOME}/go/workspace/bin && \
    mkdir -p ${HOME}/go/package ${HOME}/go/package/src ${HOME}/go/package/pkg ${HOME}/go/package/bin && \
    mkdir -p ${HOME}/go/workspace/src/minq && \
    go get -u github.com/labstack/echo/...

Golang + Chrome on CentOS7(Docker)でスクレイピング
How to Install Go on CentOS 7
Download the Go distribution

Goの環境変数のまとめ

環境変数 用途
GOROOT Goバイナリのパス
GOPATH 作業フォルダのパス

GOROOT

GOROOTは指定する必要は特にないそうです. 例えば複数バージョンのGoをインストールした場合にGOROOTを使うとバイナリのパスを取得できます.

$ go env GOROOT
/usr/local/go

Installing extra Go versions

GOPATH

GOPATHはデフォルト値として$HOME/goが指定されています.

$ go env | grep GOPATH
GOPATH="/home/vpsuser/go"

getサブコマンドで外部からパッケージを取得する場合のインストール先もGOPATH直下になります. つまり/home/vpsuser/go以下に展開されます.

If the specified package is not present in a workspace, go get will place it inside the first workspace specified by GOPATH.

Remote packages
How to Write Go Code

Echo

WebサーバーとしてEchoというのを使います. Quick Startを参考にserver.goというファイルをcontextフォルダに作成します. プロジェクト名はminqとします.

COPY server.go ${HOME}/go/workspace/src/minq

ビルドした後コンテナを起動してポートを転送しておきます.

docker run -it --rm  -p 1323:1323 minq:0.1

コンテナに入ってコマンドプロンプトに以下のコマンドを打ち込みましょう.

$ go install minq

これで${HOME}/go/workspace/bin以下にminqという実行可能バイナリが出力されます. すでにbinフォルダのパスは通っているので, コマンドラインから以下を実行するとサーバーが起動します.

$ minq

まとめ

これまでの作業をまとめるとこんな感じでしょうか.

FROM centos:7 AS builder

RUN yum -y update && yum -y install \
    useradd \
    usermod \
    chpasswd \
    sudo \
    wget \
    which \
    git \
    yum clean all

ARG GO_VERSION=1.13.4

WORKDIR /usr/local/src
ADD https://dl.google.com/go/go${GO_VERSION}.linux-amd64.tar.gz ./
RUN tar -C /usr/local/ -xzf go${GO_VERSION}.linux-amd64.tar.gz && \
    rm /usr/local/src/*.tar.gz

ENV USER_NAME vpsuser
ENV HOME=/home/${USER_NAME}

RUN useradd --create-home ${USER_NAME} && \
    usermod -aG wheel ${USER_NAME} && \
    echo "${USER_NAME}:tekitou" | chpasswd

# ENV GOROOT=/usr/local/go
ENV PATH=$PATH:/usr/local/go/bin

# ENV GOPATH=$HOME/go
# ENV PATH=$PATH:$GOPATH/bin
ENV GOPATH=${HOME}/go/package:${HOME}/go/workspace
ENV PATH=${HOME}/go/go/bin:${HOME}/go/workspace/bin:${PATH}

USER ${USER_NAME}
WORKDIR ${HOME}

RUN mkdir -p ${HOME}/go/workspace ${HOME}/go/workspace/src ${HOME}/go/workspace/pkg ${HOME}/go/workspace/bin && \
    mkdir -p ${HOME}/go/package ${HOME}/go/package/src ${HOME}/go/package/pkg ${HOME}/go/package/bin && \
    mkdir -p ${HOME}/go/workspace/src/minq && \
    go get -u github.com/labstack/echo/...

COPY server.go ${HOME}/go/workspace/src/minq

Reference

ネコでもわかる!さくらのVPS講座
Linuxセキュリティ入門
Docker入門

Docker : firewalldでssh, http, httpsのみ許可する。(CentOS 7)

ゲームを題材に学ぶ 内部構造から理解するMySQL
MySQL道普請便り

ツイッターでシェア
みんなに共有、忘れないようにメモ

ブレイン

Androidアプリ開発者を目指しています. 興味あることリスト: https://t.co/ew3bb6grdJ Github: https://t.co/9btqysHqWr Qiita: https://t.co/ZVRhjouauX

Crieitは誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。

また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!

有料記事を販売できるようになりました!

こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?

コメント