Разворачивание golang приложения с помощью gitlab-ci и docker swarm

У меня есть несколько своих проектов, которые соответственно приходится самому поддерживать разворачивать и тп, к примеру данный блог и другие простые проекты. Когда я использовал symfony и php, развернуть можно просто с помощью capifony, даже не зная Ruby. В связи с тем что я решил перейти на golang то данные инструменты не совсем подходят. Почему не использовать к примеру в проектах тот же php, чаще быстрее разрабатывать на том что используешь в работе. Для начала решил попробовать обычный способ с помощью docker swarm и gitlab ci, есть конечно решения с heroku, но я пока решил использовать vps например vscale. K8s для маленьких проектов возможно будет излишним.

Мы будем использовать Centos, Swarm вместе с gitlab-ci. В данной статье будет только базовое разворачивание, возможно в дальнейшем мы развернем stateless сервисы и также добавим миграцию для базы.

Выбираем любой сервис vps и устанавливаем на него centos 8 64bit.

Установка и настройка Swarm

Есть некоторые особенности установки docker на centos8, вы можете использовать любую другую систему, или некоторые севисы сразу предоставляют систему с установленным docker.

# dnf config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo
# dnf --disablerepo=AppStream install -y container-selinux docker-ce
# systemctl start docker
# systemctl enable docker
# firewall-cmd --zone=public --add-masquerade --permanent
# firewall-cmd --reload

Инициализация swarm - командой docker swarm init.

Настройка проекта

В качестве примера я буду использовать проект gitlab.com/4devs/gqlgen-todos

$ mkdir ~/go/src/gitlab.com/4devs/gqlgen-todos
$ cd ~/go/src/gitlab.com/4devs/gqlgen-todos
$ go mod init gitlab.com/4devs/gqlgen-todos

Добавляем базовую схему

$ echo 'type Todo {
  id: ID!
  text: String!
  done: Boolean!
  user: User!
}

type User {
  id: ID!
  name: String!
}

type Query {
  todos: [Todo!]!
}

input NewTodo {
  text: String!
  userId: String!
}

type Mutation {
  createTodo(input: NewTodo!): Todo!
}' >> schema.graphql

Генерация всего сервера командой go run github.com/99designs/gqlgen init, запустить можно командой go run ./server/server.go.

В файле resolver.go мы заменим panic("not implemented") на строке 25 на return nil,nil, чтобы не получать паники.

Настройка Gitlab CI

Добавим файл для создания образа из нашего проекта mkdir build && vim build/Dockerfile таким содержимым

FROM scratch

COPY bin/server . 
CMD ["/server"]

 

У нас будет три этапа в проекте:

  • build, соберем проект
  • image, создадим docker образ с бинарником проекта
  • deploy, обновление образа на сервере

На рабочем проект конечно должно быть больше этапов как минимум lint и test.

Получается .gitlab-ci.yml с таким содержимым

stages:
    - build
    - image
    - deploy

services:
    - docker:dind

build:
    stage: build
    image: golang:1.13
    before_script:
        - pwd
        - ls -la
    script:
        CGO_ENABLED=0 GOOS=linux go build -o bin/server server/server.go
    artifacts:
        paths:
            - bin/
        expire_in: 1 day
        
create image:
    stage: image
    image: docker:latest
    before_script:
        - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    dependencies:
        - build
    script:
        - pwd
        - ls -la
        - docker build -t ${CI_REGISTRY_IMAGE}:${CI_PIPELINE_ID} -f ./build/Dockerfile .
        - docker push ${CI_REGISTRY_IMAGE}:${CI_PIPELINE_ID}
        - docker tag ${CI_REGISTRY_IMAGE}:${CI_PIPELINE_ID} ${CI_REGISTRY_IMAGE}:latest
        - docker push ${CI_REGISTRY_IMAGE}:latest

deploy stage:
    stage: deploy
    when: manual
    tags:
        - stage
    before_script:
        - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    script:
        - pwd
        - ls -la
        - docker system prune -f
        - docker pull ${CI_REGISTRY_IMAGE}:latest
        - docker service update --image ${CI_REGISTRY_IMAGE}:latest --force api

Настройка разворачивания проекта на сервере

Добавляем gitlab-runner как контейнер

# dnf install -y git
# curl -LJO https://gitlab-runner-downloads.s3.amazonaws.com/latest/rpm/gitlab-runner_i686.rpm
# rpm -i gitlab-runner_i686.rpm 

Запуск runner

# docker run -d \
    --name gitlab-runner \
    --restart always \
    -v gitlab-runner:/etc/gitlab-runner \
    -v /var/run/docker.sock:/var/run/docker.sock \
    gitlab/gitlab-runner:latest

Настройка runners, токен нужно скопировать из CI / CD Settings->Runners

# docker exec -it gitlab-runner gitlab-runner register -n \
	--url https://gitlab.com/ \
	--registration-token <токен> \
	--executor docker \
	--description "docker-build-runner" \
	--docker-image "docker:latest" \
	--docker-privileged \
	--docker-volumes /var/run/docker.sock:/var/run/docker.sock \
	--docker-pull-policy "always" \
	--tag-list stage

После этого runner должен появится в списке доступных.

Создание проекта на сервере

Так как наши команды CI обновляют сервис, нам необходимо первый раз его создать, можно сделать командой docker service create --name api --publish published=80,target=8080 registry.gitlab.com/4devs/gqlgen-todos, проверить можно по ip сервера.

В вкладке Pipelines нам доступен ручной деплой сервиса.

Выводы

Показан один из простых вариантов разворачивания сервиса на vps. В дальнейшем планируется разворачивания базы данных и добавления дополнительных этапов в CI, таких как тестирование и линтер. Так же данная структура проекта была взята без изменений, в рабочем проекте обычно применяется другая. Скачать проект можно по ссылке Init Swarm

Дополнительно по теме можете прочитать несколько статей:

Читайте также:

Введение в go mod

С недавнего времени в golang появилась система управления зависимости. В данной статье будут только основные моменты, как можно решать базовые задачи управления зависимостями.

Объединение строк в golang

Одной из частых операций может оказаться объединения(concatenation) строк, есть много библиотек для решения задач, мы рассмотрим несколько самых распространенных примеров.