この記事は前の出版で提起された道具のトピックの続きです。 本日は、リソースの依存関係を含む単一の実行可能ファイルの形式でのGolangアプリケーションリリースのアセンブリ、および最終アセンブリのサイズの最適化の問題を扱います。 また、次の要件を満たす作業環境を構築するプロセスも検討します。
- 移植性。 環境は、さまざまなマシンで簡単に再現できる必要があります。
- 分離。 環境は、開発者のマシンにインストールされているライブラリとプログラムのバージョンに影響を与えません。
- 柔軟性。 この環境では、異なるバージョンのGolangとLinux(異なるバージョンのディストリビューションとglibc)のリリースを収集できる必要があります。
- 再現性 魔法や秘密の知識があってはなりません。つまり、プロジェクトと依存関係を構築するすべての手順をコードで記述する必要があります。
はじめに
Golangは、静的アプリケーションを構築する方法を提供します。 非常に便利で、多くの場面に適しています。 Webインターフェースまたは本格的なWebサービスを備えたユーティリティを開発する場合、依存関係は必然的に次のものに現れます。
- 構成ファイル
- ユーザーインターフェイス用のテンプレート、スタイル、スクリプト、および画像
- サービスがhttpsで直接動作する場合、SSLのキーなどの機密情報
- などなど
最終リリースが次のセットになるように、必要なリソースを実行可能ファイルに静的にパックできると便利です。
- サービス構成ファイル
- 実行可能リリースファイル
- systemdまたは同等のサービス構成ファイル。
このアプローチにより、いくつかの問題を一度に解決できます。
- エンドユーザーへのコード配信を簡素化します。
- リソース制御を提供する
- セキュリティを確保する(第三者による変更の不可能性)
たとえば、mailhog、consul、vaultなどの一般的なオープンソースプロジェクトの作成者が、同様のアプローチを使用しています。
作業環境
異なるバージョンのコンパイラと依存関係を持つ複数のプロジェクトを同時に実行している場合、おそらく同じ開発者のマシン内で異なるプロジェクトの作業環境を分離する方法をすでに見つけているでしょう。
私の実践では、さまざまな言語で記述され、同時に配信に含まれるコンポーネントで構成されるハイブリッドシステムが広く使用されているため、それらの間の切り替えの柔軟性と速度に関する追加の疑問が生じます。
前の記事で、Erlangプロジェクトの作業環境を設計する際の基本的なアイデアの説明を見つけることができます。 サンドボックスも、Golangプロジェクトの同様の原則に基づいて構築されています。
Golangプロジェクトを開発するためのデモアプリケーションと環境のソースコードは、 https://github.com/Vonmo/relgoにあります。
注:コードはdebianライクなディストリビューションでテストされています。 この記事を正常に機能させるには、マシンにdocker 、 docker-composeおよびGNU Makeをインストールする必要があります。 Dockerのインストールにはそれほど時間はかかりません。ユーザーをdockerグループに追加する必要があることを覚えておく必要があります。
makeでは、次の目標が事前に定義されています。
- build_imgs 。 彼は、作業環境のコンテナーを起動するための基本コンテナーの組み立てを担当しています。
- アップ 。 サンドボックスコンテナを作成または更新してから起動します。
- ダウン 。 サンドボックスコンテナを停止および削除します。
- テスト 。 テストを実行する
- rel 。 アプリケーションの最終リリースを準備します。
- を実行します。 サンドボックスでアプリケーションを起動します
- deps 。 依存関係を取得および更新します
- new_migration データベース移行を作成します
- 移行します。 移行を適用
- format_code gofmtでソースコードをフォーマットする
デモアプリケーション
アトミックカウンターを開発します。 例として、データベースの依存関係を入力してアプリケーションを複雑にします。 この場合、カウンター値はpostgresqlに保存されます。
要件を定義します。
- 機能性
アプリケーションは次の機能を実行する必要があります。
- インクリメント
- デクリメント
- リセットする
- 価値
- 対象システム:ubuntu 16.04 LTS
- シンプルなHTTP API
- Golangの使用> = 1.9
- アプリケーションの内部プロセスを監視するための初期インターフェイスの存在
建築
アプリケーションのアーキテクチャは、論理的に完全なユニットを個別のパッケージに分離するという考え方に基づいています。 アプリケーションには次のレイヤーがあります。
- core-システムのコアであり、基本機能を提供します:構成、ロギング、メトリック。 正しい開始および停止サービスを提供します。
- config-構成の解析を担当
- モデル-データベース通信インターフェースを実装します。
- 移行-データベーススキーマの移行。
- log-ロギングレベルを標準ログに追加します。
- メトリック-シンプルなアプリケーションメトリックを実装します。
- services-システムのすべてのサービスを含むパッケージ。
レイヤーとアプリケーションのサービスへの分割により、システムに新しい機能を簡単に追加でき、さらに、分散環境でリリースを柔軟に構成できます。
コア
カーネルの主な目標は、接続されているすべてのサービスの基本機能を備えた正しい環境を作成することです。 デモアプリケーションは、カーネル関数を実装します。
- 構成
- ロガー
- 指標
- サービスレジストリ
ノード構成
最も単純なユーティリティの場合、コマンドライン変数とフラグメカニズムを使用できます。 より複雑なプログラムの場合、構成ファイルの導入が正当化されます。
yaml形式に感心しているため、この例で使用されていますが、Parse / 1のconfig.go関数と属性自体の単純な改良でパーサーを置き換えることができます。これらの属性は、構成構造の宣言で解析を行います。
構成全体は、論理的に関連するパラメーターをグループ化するセクションに分割されます。
- ノード-ノードの名前とクラスター内のノードの位置(ノード名、データセンター、シェルフ)を決定するパラメーターのグループ
- ランタイム-並列化の度合いなど、ランタイムを設定できます。
- Dirs-カーネルが自動的に作成するディレクトリのリストを設定します。 同時に、コードでそれらにアクセスするのは非常に簡単です。データフォルダーへのパスを取得する例はSystem.Config.Dirs.Dataです。
- ログ-ロガーのパラメーターを定義します。 ログをstdoutまたはファイルに書き込むことができますが、ログレベルによってイベントの数を柔軟に調整できます。
- メトリック-アプリケーションのメトリックオプションを提供します。 メトリックファイルを使用すると、たとえば、イベントが多すぎたり少なすぎたりする場合に、アプリケーション内で行われているプロセスを把握できます。 メトリックは、システムにリクエストを送信し、リクエストの処理中にカウンタがどのように変化したかを確認できるため、テストにも役立ちます。
- DataSources-データベースなどの外部データソースに接続するためのパラメーターが含まれています。
- サービス-サービスごとに特定のオプションセットを定義できます。
サービス
サービスを正しく初期化するには、いくつかの条件を満たす必要があります。
標準の説明インターフェイスを継承して、サービスの構造の説明を実装します。
私たちのサービスの例:
type ACounter struct { core.Service http *http.Server }
init()
定義して、サービスインスタンスが作成および起動さinit()
関数を定義しinit()
。
func init() { go (&ACounter{ Service: core.NewService(Acounter), }).start() }
- サービスを開始および停止するロジックを実装する
func (srv *ACounter) start() { waitCore() srv.Ready = true srv.ShutdownFun = func(reason string) { log.Debug("acounter: soft shutdown") ... } core.Register(&srv.Service) ... }
サービスを登録するには、標準のinit()
呼び出しを使用します。 モジュールでinit()
呼び出される順序を保証できないため、カーネルは待機メカニズムを提供します。
-
core.WaitCore()
-呼び出しはカーネルの完全な初期化を待機しています:メトリックの設定、ロガー、データベースおよび他の内部プロセスへの接続 -
core.WaitService(srv string)
-別のサービスの初期化が完了するまで待つことができます。 このメカニズムにより、関連サービスの開始順序を決定できます。
オペレーティングシステムから停止信号を受信した場合、カーネルはsrv.ShutdownFun
呼び出して、これをすべてのサービスに報告します。
依存関係管理
Depはベースイメージに含まれています。 このユーティリティはベンダーのロックを許可し、現在は標準です。 プロジェクトに依存関係を作成するには、プロジェクトファイルに依存関係をインポートし、定義済みの目標を実行します。
-
$ make deps
すべての新しい依存関係は、ベンダーディレクトリに配置されます。 また、Depは未使用の依存関係を自動的に削除します。
ロガー
goロギング用のライブラリはたくさんあります。 たとえば、google glogはカスタマイズ可能で、レベルがあり、素晴らしい仕事をします。 しかし、私はプロジェクトに追加の依存関係を望んでおらず、多くのライブラリは何らかの理由で適合しませんでした。
解決策として、次の要件を満たすために標準ログを拡張する必要がありました。
- 元のログAPIを実装しました。
Log。*通話は機能するはずです。 - 既存のプロジェクトで簡単に切り替えることができます。
imprt “log”
はimport "github.com/Vonmo/relgo/log"
置き換えられimprt “log”
- 表示されるマイクロ秒のタイムスタンプ。
- ファイル名と、ロギング機能が呼び出された行が表示されました。
- デバッグ、情報、エラー、パニック、致命的なメッセージを入力し、ログレベルを設定しました。
移行
プロジェクトにはデータベース依存関係があり、postgresqlにはデータスキーマがあるため、移行は良い習慣です。 移行のアプローチは異なります。プロジェクト言語のフレームワークでそれらを記述し、移行およびロールバック機能を実装するか、追加のユーティリティを使用できます。
ツールの選択は、開発者の好みとプロジェクトの環境に依存します。 同時に複数の言語で作業し、移行の均一性が必要な場合は、外部ユーティリティを使用できます。 私自身はsql-migrateを選択しました。 このユーティリティはプレーンテキストを生成します。このテキストには、移行とロールバックのSQLを配置する必要があります。
テスト中
goの世界では、ハンドラーを直接呼び出すことにより、サーバーを起動せずにhttp apiをテストすることを提供しています( https://golang.org/pkg/net/http/httptest/を参照)。 ただし、統合テストに近づくために、単体テストから離れて、完全なインターフェイステストを実行する必要があります。
この例では、テスト環境で完全なカーネルとすべての特定のサービスをテストする実装を見つけることができます。
注:十分に検索できていませんが、ErlangのCommon Testと機能が似ているGolangのテストフレームワークはありますか?
合計ファイルサイズ
問題は、なぜGolangアセンブリがそんなに大きいのかということです。 たとえば、単純なmain(){ fmt.Println("done.") }
まとめると、約1.9 MBが得られ、そのうちランタイムgolangは約1 MBを消費します(データはgo 1.9.2 amd64に関連します)。
実際のプロジェクトでは、さらに興味深いことがあります。
- consul-Linux-x86_64-42.8 Mb
- vault-Linux-x86_64-75.5 Mb
- mailhog-Linux-x86_64-11.2 Mb
安定したコードがリリースされているため、ビルドオプションとして-wおよび-sキーを指定することにより、DWARFデバッグ情報と一般的なデバッグ情報を無効にできます。 この方法により、サイズが約30%削減されます。
次のステップは、実行可能ファイルパッカーを使用することです。 人気のあるパッカーの1つはupxです。 パッカーを使用した後、最終的なビルドサイズは初期の1.9 Mbから423 Kbに縮小されたため、ソースファイルのサイズはほぼ78%削減されました。 あなたはすでにそれで生きることができます。
まとめ
アプリケーションを簡単に開発できる環境を作成できました。 Erlangの作業環境と同様に、この環境では、開発中のマシンに関係なく重複した結果を取得し、ライブラリとツールのバージョンの競合を取り除き、さらにCIを簡素化し、最も重要なこととして、開発の初期段階で問題を特定できます。 Dockerは非常に複雑な環境を再生できるためです。
読者の皆様、このトピックに時間と関心をお寄せいただきありがとうございます。