Let's take a look at compiling a project in GO and how it works in Gitlab CI.
Compilation
To build, we need to call go build -o binary_name
.
If your project has imports from third-party repositories, then there are external dependencies. Compiling for GO will require all source code, including dependency library code. Therefore, you need to make sure that third-party libraries that may not be stored in your repository are also available for compilation.
Vendor or download
At the moment, the current version is 1.14.2. Starting from version GO 1.11, module functionality (go modules
) is available.
GO modules work in such a way that GO itself loads all third-party libraries when go run
called , go build
or when all libraries are explicitly loaded - . go get ./...
The source code of downloaded libraries is saved to $GOPATH/pkg/mod
or to $HOME/go/pkg/mod
if an environment $GOPATH
variable is not set.
There is an approach to dealing with dependencies called vendoring. GO modules support vendoring so that if you specify -mod=vendor_dir
the , the dependencies will be downloaded to the vendor_dir
folder. This folder can be inside your repository and pushed into the Git repository along with your source code.
When using vendoring, dependencies will be downloaded once and shipped with the source code of the project, which will allow for faster builds.
Gitlab
Gitlab provides the ability to perform various tasks after pushing to the repository.
To use this functionality, you need to add .gitlab-ci.yml
the .
Compilation in Gitlab
You need to define the stages and the specific actions to be performed in those steps. For each action, you can specify a docker image.
Your project code will be automatically downloaded to the /builds/{project_group}/{project_name}
folder. This means that there is no need to download it manually.
In the code below, I've defined the build step and the eponymous action in it:
stages:
— build
build:
image: rhaps1071/golang-1.14-alpine-git
stage: build
script:
— go get ./...
— GOARCH=amd64 GOOS=linux go build -ldflags "-extldflags '-static'" -o $CI_PROJECT_DIR/binary
artifacts:
paths:
— binary
The docker image of my authorship rhaps1071/golang-1.14-alpine-git is used here.
It adds a command git
to the golang:1.14-alpine
. This modification is necessary for the command go get ./...
that loads dependencies and uses git clone
the .
The base image is based on Alpine Linux, as this distribution weighs only a few megabytes.
However, the size golang:1.14-alpine
weighs in at 370MB, which is a lot, but still a two-fold gain compared to golang:1.14
the (809MB) based on Ubuntu.
I don't use vendoring, so I need to download dependencies to build. To do this, call the commandgo get ./...
.
As I said above, the code in Gitlab CI is located in the , which is outside $GOPATH
the /builds/{project_name}/{project_folder}
.
In order for this to work, there must be a . go.mod
You can create it by using go mod init
the . If you don't have a file go.mod
, the team go get ./...
won't be able to figure out the dependencies.
If your project doesn't use GO modules, you'll need to move your source code to the $GOPATH
.
In Gitlab CI, this copy will look like this:- cp /builds/* $GOPATH/src/
The artifacts statement allows you to save any of the .gitlab-ci.yml
files or folders for download, as well as for use in the next stages of Gitlab CI.
SSH
Deployment Let's take a look at the deployment of the resulting binary using SSH.
deploy_stage:
image: kroniak/ssh-client
stage: deploy
environment:
name: stage
url: http://stage.project.com
when: manual
script:
— mkdir -p ~/.ssh
— echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
— chmod -R 700 ~/.ssh
— echo "$SSH_KNOWN_HOSTS" >> ~/.ssh/known_hosts
— chmod 644 ~/.ssh/known_hosts
— echo "$CONFIG" > ./config.json
— scp -P$SSH_PORT -r ./config.json $SSH_USER@$SSH_HOST:/var/www/project/config/
— scp -P$SSH_PORT -r ./binary $SSH_USER@$SSH_HOST:~/binary_tmp
— ssh -p$SSH_PORT $SSH_USER@$SSH_HOST 'sudo service project stop && cp ~/binary_tmp /var/www/project/binary && sudo service project restart'
Here again, a customized Alpine Linux image is used — 12.1MB kroniak/ssh-client
in size. This time, it additionally pre-installed with the ssh client, thanks to which ssh
scp
the commands and .The deployment logic is as follows:
- The following Gitlab variables are enabled:
$SSH_PRIVATE_KEY
- private key to access the server;$SSH_USER
,$SSH_HOST
,$SSH_PORT
is the login and address of the server for deployment;$SSH_KNOWN_HOSTS
- An entry for the .ssh/known_hosts file through which the server is validated.$CONFIG
– the contents of our service's configuration file in json format; - When deploying, first fill in all the necessary files with data from Gitlab variables;
- All the necessary files are copied to the server using
scp
. Could be usedrsync
as it only copies files that have changed and copies them in archived form. However, when it comes to 1-2 files, there is almost no winning. - The last step is to replace the binary and restart our service;
The resulting file .gitlab-ci.yml
looks like this in its entirety:
stages:
— build
— deploy
build:
image: rhaps1071/golang-1.14-alpine-git
stage: build
script:
— go get ./...
— GOARCH=amd64 GOOS=linux go build -ldflags "-extldflags '-static'" -o $CI_PROJECT_DIR/binary
artifacts:
paths:
— binary
deploy_stage:
image: kroniak/ssh-client
stage: deploy
environment:
name: stage
url: http://stage.project.com
when: manual
script:
— mkdir -p ~/.ssh
— echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
— chmod -R 700 ~/.ssh
— echo "$SSH_KNOWN_HOSTS" >> ~/.ssh/known_hosts
— chmod 644 ~/.ssh/known_hosts
— echo "$CONFIG" > ./config.json
— scp -P$SSH_PORT -r ./config.json $SSH_USER@$SSH_HOST:/var/www/project/config/
— scp -P$SSH_PORT -r ./binary $SSH_USER@$SSH_HOST:~/binary_tmp
— ssh -p$SSH_PORT $SSH_USER@$SSH_HOST 'sudo service project stop && cp ~/binary_tmp /var/www/project/binary && sudo service project restart'