この記事は、GitLabのプライベートリポジトリでGoのパッケージの管理、CI/CDでの運用を調査したものです。
目次
GitLab CI/CD のビルド環境から外部のリポジトリへのアクセス
あるgitリポジトリから、他のプライベートなgitリポジトリへアクセスする事があります。
たとえばよくあるのは、社内で開発されているライブラリの管理をしているリポジトリを参照するためにgit サブモジュールを使用している場合等でしょうか。
開発している個人のローカル環境では、各開発者のアカウントでリポジトリへアクセスできるように正しく権限を付与するだけで良いですが、CI/CDのように特定の個人に依存しないDocker等の環境からアクセスしたい場合、メンバーのアカウント情報に依存しないアクセス方法が欲しくなります。
今回は、GitLab の CI/CD 環境(Docker executor)から必要なリポジトリへ適切な権限でアクセスする方法を紹介します。
また、実例としてGo言語で実装した複数の AWS Lambda から、別のリポジトリで管理している自作のGo言語のパッケージを参照する方法も紹介します。
GitLab CI/CDの詳細についてはこちらの記事を参照してください。 blog.linkode.co.jp
Deploy Tokens と Deploy Keys
GitLab では、ユーザー名とパスワード無しでリポジトリへアクセスする手段として、トークンを発行するDeploy Tokens
と、SSH Keyを使用するDeploy Keys
が用意されています。
トークンを発行する方法はGitLabに依存するので、今回は特定のホスティングサービスに依存しない、汎用的に活用できるSSH Keyで設定を行います。
SSHキーのペア作成
ssh-keygen
コマンドで、SSHキーのペアを作成します。
以下の例では、暗号化の方式はRSAとして、ファイル名はgitlab_mylib_rsa
としています。
$ ssh-keygen -t rsa -f gitlab_mylib_rsa
パスフレーズは空で作成しました。
コマンドを実行したフォルダー内に秘密鍵gitlab_mylib_rsa
と公開鍵gitlab_mylib_rsa.pub
のふたつのファイルが作成されます。
アクセスされる側(Deploy Keys)の設定
SSHキーのペアが作成出来たら、アクセスを行いたいリポジトリのDeploy Keys
へ、公開鍵を設定します。
ブラウザでGitLabのターゲットとなるリポジトリのページを開き、左側のメニューのSettings
から
Settings
-> Repository
を選択すると、表示される項目の一覧の中にDeploy Keys
を見つけることができるので、Expand
ボタンを押して、項目の詳細を開きます。
Titleに、任意のタイトルを入力して、Keyに作成した公開鍵ファイル(gitlab_mylib_rsa.pub
)の内容をそのまま貼り付けます。
デフォルトではアクセス権がRead Onlyとなっているので、リポジトリに対してPUSH等をさせたい場合はWrite access allowedへチェックを入れます。
今回は取得のみ行うので、チェックは入れていません。
アクセスする側(CI/CD)の設定
今度はアクセスする側のリポジトリで、CI/CDの環境変数に秘密鍵を登録します。
Settings
-> CI / CD
でCI/CDの設定画面を開き、Variables
から設定を行います。
Expand
ボタンで詳細を開き、Add Variable
ボタンで変数を設定するモーダルUIが表示されます。
環境変数の名前(Key)をSSH_PRIVATE_KEY
として、値(Value)に秘密鍵(gitlab_mylib_rsa
)の内容を貼り付けます。
TypeはVariable
、FlagsのProtect variable、Mask variableのチェックは外しておきます。
これで、環境変数として秘密鍵を取得できます。
※安全のためにMask variableはチェックを入れたいところですが、制約があり、マスクすることができません。
https://docs.gitlab.com/ee/ci/variables/README.html#mask-a-custom-variable
.gitlab-ci.yml
環境変数が設定出来たら、.gitlab-ci.yml
へ次のように設定することで、Deploy Keys
を設定したリポジトリへアクセスできます。
image: amazon/lambda-build-node10.x:latest stages: - build before_script: # ssh-agent を実行します。 - eval `ssh-agent` # 秘密鍵の登録を行います。 - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - # known_hostsを格納するためのSSHのフォルダーを作成します。 # ssh-keyscan で対象ホスト(gitlab.com)のホストキー一覧を取得してknown_hostsへ追記します。 - mkdir -p ~/.ssh - ssh-keyscan gitlab.com >> ~/.ssh/known_hosts - chmod 644 ~/.ssh/known_hosts build: stage: build script: # SSHプロトコルでライブラリのリポジトリをクローンすることができます。 - git clone git@gitlab.com:my-group/my-subgroup/my-library.git
Dockerのイメージは仮で、AWS Lambdaをビルドすることを想定して、amazon/lambda-build-node10.xにしています。
また、クローンしているリポジトリも仮に git@gitlab.com:my-group/my-subgroup/my-library.git
としています。
SSHに関するスクリプトをbefore_script
へ書いていますが、必要なジョブのみに記述しても問題ありません。
Go の場合
ここまでで、GitLabのCI/CDを行うDocker環境からSSHプロトコルで他のリポジトリへアクセスする方法がわかりました。
ここからは、Deploy Keysの設定を踏まえて、Go言語で作成した自作のパッケージを複数のAWS Lambdaで使用するケースを紹介します。
想定と環境
Go言語で実装しているAWS Lambda関数から、自作のパッケージを参照します。
Goのバージョンは 1.14.1 を使用して、Lambda関数も自作のパッケージもGitLabのプライベートリポジトリで管理します。
また、Goのビルドにmakeを使用し、Lambda 関数とレイヤーのデプロイに Serverless Framework を使用しています。
Serverless Framework はNode.jsで作られたCLIツールで AWS Lambda、Azure Functions、Google Cloud Functions等の Serverless Applicationを構成管理、デプロイするためのツールです。
各ツールのインストールや環境構築については割愛します。
import文
Goはホスティングサービス上に配置されたパッケージをimport
できます。
https://golang.org/cmd/go/#hdr-Remote_import_paths
ドキュメント内に例示されていますが、GitHubだと次のようにimport
します。
import "github.com/user/project" import "github.com/user/project/sub/directory"
ただ、パッケージがGitLabのサブグループに配置されている場合、注意が必要です。
たとえば、パッケージがgitlab.com/group/subgroup/my-project
というリポジトリへ配置されている場合、GitHubの例のように
import "gitlab.com/group/subgroup/my-project" import "gitlab.com/group/subgroup/my-project/sub/directory"
と記述してしまうと、subgroup
の部分がリポジトリとみなされてしまい、ビルドエラーとなってしまいます。
これを回避するためには、リポジトリを識別するProject slug
の部分に.git
を付与します。
import "gitlab.com/group/subgroup/my-project.git" import "gitlab.com/group/subgroup/my-project.git/sub/directory"
これについては、GitLabへissueが挙がっていますが、現時点では.git
を付与してリポジトリを明示する形が良さそうです。
https://gitlab.com/gitlab-org/gitlab-foss/-/issues/1337
https://gitlab.com/gitlab-org/gitlab-foss/-/issues/30785
Gitの設定(insteadOf)
Goの依存モジュールを管理するGo Modulesは、import
された対象のパッケージリポジトリをHTTPSスキームのURLで取得します。
このため、取得時にSSHスキームとなるようにGitの設定を行います。
$ git config --global url."ssh://git@gitlab.com".insteadOf "https://gitlab.com"
GOPRIVATE
Go1.13からデフォルトでプロキシを経由してパッケージを取得するようになっています。
社内リポジトリなどのパブリックに取得できないパッケージの場合、環境変数GOPRIVATE
を設定して、プロキシを介さず直接パッケージにアクセスできるよう設定する必要があります。
https://golang.org/ref/mod#module-proxy
https://golang.org/ref/mod#environment-variables
今回は、GitLabでホスティングしているプライベートリポジトリが対象となるため、次のように設定します。
$ export GOPRIVATE=gitlab.com/*
プロジェクトの配置とCI/CDからのデプロイ
ここまでの内容を踏まえて実際にプロジェクトを配置してみます。
ライブラリ側(アクセスされる側)
リポジトリ
パスはgitlab.com/test-go-package-group/some-subgroup/my-golang-library
として、Deploy Keys
を設定します。フォルダー構成
./ └─libhello/ go.mod get_hello.go
- libhello/go.mod
goコマンド
go mod init gitlab.com/test-go-package-group/some-subgroup/my-golang-library.git/libhello
でプロジェクトを初期化しています。
module gitlab.com/test-go-package-group/some-subgroup/my-golang-library.git/libhello go 1.14
- libhello/get_hello.go
Hello!
という文字列を取得するだけのライブラリです。
package libhello // GetHello return "Hello!" func GetHello() string { return "Hello!" }
AWS Lambda側(アクセスする側)
リポジトリ
パスはどこでも良いのですが、gitlab.com/test-go-package-group/golang-lambda-functions
として、CI/CDの環境変数にSSH_PRIVATE_KEY
を設定します。フォルダー構成
ServerlessFrameworkのコマンドsls create --template aws-go
でプロジェクトのテンプレートを作成して、
go mod init gitlab.com/test-go-package-group/golang-lambda-functions.git
でプロジェクトを初期化します。
./ ├─hello/ │ main.go ├─world/ │ main.go ├─ .gitlab-ci.yml ├─ go.mod ├─ go.sum ├─ Makefile └─ serverless.yml
- ソースコード(
hello/main.go
およびworld/main.go
)
2つのLambda関数のハンドラーは、ほとんどテンプレートのままですが、ライブラリをインポートして使用するようにしています。
2つの関数の見分けがつくように、出力の文字列をそれぞれ別のものに変更しても良いです。
package main import ( "fmt" "log" "github.com/aws/aws-lambda-go/lambda" // 自作のパッケージをインポート "gitlab.com/test-go-package-group/some-subgroup/my-golang-library.git/libhello" ) func Handler() { // libhello のGetHello() から文字列を取得してログ出力 hello := libhello.GetHello() log.Println(fmt.Sprintf("libhello Get [%s]", hello)) } func main() { lambda.Start(Handler) }
.PHONY: build clean deploy export GO111MODULE=on export GOOS=linux export GOPRIVATE=gitlab.com/* build: go build -ldflags="-s -w" -o bin/hello/app.lambda_handler hello/main.go go build -ldflags="-s -w" -o bin/world/app.lambda_handler world/main.go clean: rm -rf ./bin deploy: clean build sls deploy --verbose
serverless.yml
特筆する事項はありませんが、Makefileで指定している実行バイナリの出力パスとハンドラーのパスをあわせておきます。.gitlab-ci.yml
CI/CDでLambda関数のビルドとデプロイを行います。
SSHのセットアップは、アンカーで記述し使いまわせるようにして、ビルドにはGoのイメージを、デプロイにはServerlessFrameworkを使用したいのでNodeのイメージを指定しています。
SSHのセットアップの最後に、SSHスキームでGitLabへアクセスするようにGitの設定(git config
)も行っています。
stages: - build - deploy .setup_ssh: &setup_ssh - eval `ssh-agent` - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - - mkdir -p ~/.ssh - ssh-keyscan gitlab.com >> ~/.ssh/known_hosts - chmod 644 ~/.ssh/known_hosts - git config --global url."ssh://git@gitlab.com".insteadOf "https://gitlab.com" build: image: golang:1.14 stage: build script: - *setup_ssh - make cache: paths: - bin deploy: image: node:14.10-alpine stage: deploy script: - yarn config set prefix /usr/local - yarn global add serverless - sls deploy cache: paths: - bin policy: pull
Lambda関数の動作確認
これで、Lambda関数の管理を行っているリポジトリへgit push
すると、関数のビルド、デプロイが行われます。
ServerlessFrameworkのコマンド sls invoke --function hello --log
で動作を確認すると、ログが
libhello Get [Hello!]
と出力されていることが確認できます。
まとめ
GitLab CI/CDのDocker executorから、同じくGitLabのプライベートリポジトリへアクセスする方法と、Goで自作したパッケージの配置と使用について一例を紹介しました。
別の記事でAWS Lambda(Go) で Lambda レイヤーを活用する方法を紹介しましたが、比較的頻繁に更新されるような小さなパッケージを複数のLambda関数で使用する場合は今回の方法が有効です。