Вечер буднего дня, как же не заняться написанием статьи-заметки. В которой хочу поделиться впечатлениями о знакомстве с Go. Все что написано ниже, субъективное мнение автора. Данная статья будет полезна тем кто хочет сесть за изучение Go и окажется мало полезной для разработчиков на на нем.
Предыстория
Основной род моей деятельности — решение бизнесовых задач в среде сурового энтерпрайза на java, а по своей натуре хочется узнавать новое и интересное, для поддержки навыков, обучаться новому и не стоять на месте (этакий линейный->делец). И по этой причине давно пал взгляд на docker.
И вот на текущем проекте местами начал внедрять практики использования docker в среде разработчиков и тестировщиков. А где появляется docker, появляются контейнеры и об этих контейнерах хочется знать все от CPU до Network I/O. Для всего этого сначала пользовался стандартной связкой docker ps
и docker stats
. Были некоторые неудобства из-за того что это две отдельные утилиты, но в целом было сонно, пока не наткнулся на ctop. Думаю многим кто использует docker он знаком.
Проект
Запустив ctop на машине увидел красивые like-htop контейнеры со статистикой и возможностью посмотреть детальную информацию по контейнеру, с поиском или сортировкой. В общем я был крайне доволен.
До определенного момента, первое что хотелось бы увидеть в ctop — это healthcheck контейнера, но его не было. Потом всплыл docker swarm mode и для которого тоже хотелось бы видеть статистику по сервисам из ctop.
И тут въедливый читатель может задаться вопросом, а причем тут Go, если все про docker и личное мнение автора.
Имея опыт написания/допиливания под себя линуксовых утилит я подумал, ну раз это в терминале и это для консоли значит там должен быть python и сейчас быстренько добавим все что хочется.
Зайдя в репозиторий, удивился увидев там Go (хотя логично, docker на нем написан). Вспомнив чьи-то слова из доклада про Go, что его можно выучить за вечер, продолжил начинания в доработке программы. Почитав документацию и по запускав примеры с этого сайта сел за проект.
Примеры кода
При знакомстве с Go где-то проскочила фраза что он "лаконичен и прост в поддержке" и я соглашусь с этим высказываниями.
Первое что выглядело непривычным, но удобным — это GOPATH. Про настройку и сами понятия очень хорошо описали здесь.
Также показался интересным подход к организации и структуризации пакетов и уровне доступа. В частности объявление public и private методов через заглавную и строчные буквы и имплементация интерфейсов. И еще был несказанно рад указателям, как человек из мира java.
Управление зависимостями, по крайней мере на этом проекте не вызвало особых трудностей (хоть Go и ругали за отсутствие пакетного менеджера). Все что указывается в конструкции import
собирается в папке GOPATH в директории scr и зависимости уже можно использовать. Отдельно стоит отметить, что зависимость может быть прямо на проект с GitHub и она не сильно отличается от зависимости на какой-то собственный модуль (логика импорта сведена к минимуму):
import (
"fmt"
"strings"
"sync"
"github.com/bcicen/ctop/connector/collector"
api "github.com/fsouza/go-dockerclient"
"github.com/bcicen/ctop/config"
"context"
"github.com/bcicen/ctop/entity"
"github.com/docker/docker/api/types/swarm"
)
Что касается ctop — он оказался достаточно прост по своей структуре, за что отдельное спасибо его майнтенеру.
Структура проекта делится на три большие части:
- connector
- cwidgets
- grid/cursor
connector — интерфейс для подключения к разным сервисам предоставляемым информацию о контейнерах. Например, docker, opencontainer, kubernates и другие.
В директории connector создан класс main.go
, в котором описан интерфейс нашего коннектора:
package connector
type Connector interface {
All() container.Containers
Get(string) (*container.Container, bool)
}
А чтобы его заимплементить, нам нужно создать еще один файл, к примеру docker.go
и для метода NewDocker()
указать возвращаемый тип интерфейса и вернуть адрес для него.
А потом реализовать все перечисленные методы. Понравилось, что в рамках одного класса можно описать несколько реализаций интерфейса и это является нормальной практикой.
package docker
type Docker struct {
client *api.Client
containers map[string]*container.Container
needsRefresh chan string
lock sync.RWMutex
}
func NewDocker() Connector {
client, err := api.NewClientFromEnv()
if err != nil {
panic(err)
}
cm := &Docker{
client: client,
containers: make(map[string]*container.Container),
needsRefresh: make(chan string, 60),
lock: sync.RWMutex{},
}
return cm
}
func (cm *Docker) All() (containers container.Containers) {
cm.lock.Lock()
for _, c := range cm.containers {
containers = append(containers, c)
}
containers.Sort()
containers.Filter()
cm.lock.Unlock()
return containers
}
func (cm *Docker) Get(id string) (*container.Container, bool) {
cm.lock.Lock()
c, ok := cm.containers[id]
cm.lock.Unlock()
return c, ok
}
В скобках после func
указывается для какой структуры реализуется метод, выглядит очень удобно.
cwidgets — отдельные графические элементы из которых собирается готовый интерфейс.
grid/cursor — пакет который в себе собирает все вместе для вывода на экран и обеспечиваем методы доступа и управления.
Реализации отображения healthcheck оказалась довольна простая. В docker.go
добавили атрибут health
в мета информацию, который читаем через api аналогично функционалу docker inspect <CONTAINER>
. И в зависимости от значения меняем цвет текста в колонке NAME
.
А вот добавление swarm mode получилось более интересным.
В режиме swarm в появляются понятия node
, service
и task
. И между ними необходимо отображаться взаимосвязи, кто где находится. А это в ctop предусмотрено не было. И вот тут действительно почувствовал хорошую поддерживаемость кода. Структура контейнера содержала методы доступа к мета информации и графическому интерфейсу, которые легко выносятся в отдельный интерфейс, что и нужно для новых введенных понятий,. Интерфейс выглядит так:
package entity
import (
"github.com/bcicen/ctop/logging"
"github.com/bcicen/ctop/connector/collector"
"github.com/bcicen/ctop/models"
)
var (
log = logging.Init()
)
type Entity interface {
SetState(s string)
Logs() collector.LogCollector
GetMetaEntity() Meta
GetId() string
GetMetrics() models.Metrics
}
Здесь объявляется общая для всех классов реализующих этот интерфейс переменная log
и описываем контракт интерфейса: метод изменения состояния элемента, доступ к логу конкретного элемента через переменную log
, доступ к мета информации элемента и доступ к его метрикам.
После описания интерфейса, реализации его для всех структр и замены старого container
на новый entity
в методах grid
, всплыл один интересный вопрос. Docker на данный момент не предоставляет возможности получить информацию об использовании ресурсов с удаленной ноды средствами api. Поэтому для себя остановился на реализации вывода структуры swarm кластера и healthcheck запущенных тасков.
Впечатление и заключение
В целом от использования Go в проекте остался очень доволен, язык действительно сделан практичным и лаконичным. Он чем-то напомнил смесь Python и C++, из которых постарались взять все самое лучшее. Выучить его действительно можно за вечер. Но чтобы начать писать на нем за вечер надо иметь достаточный бэкграунд, и советовать его первым языком для изучения я бы не стал. К пониманию его удобства надо наверное придти. Также для себя выделил нишу для языка в виде написания сервисов (он наверное для этого и проектировался).
Что касается ctop, то пулл-реквест с отображением healthcheck майнтенер принял, сейчас планирую закончить отображение структуры swarm. Буду рад предложениям как реализовать считывание метрик контейнеров с удаленных нод.
Автор: Александр Козленков