Go, go, go… Первые впечатления

в 5:55, , рубрики: ctop, docker, Go, open source

Вечер буднего дня, как же не заняться написанием статьи-заметки. В которой хочу поделиться впечатлениями о знакомстве с Go. Все что написано ниже, субъективное мнение автора. Данная статья будет полезна тем кто хочет сесть за изучение Go и окажется мало полезной для разработчиков на на нем.

Предыстория

Основной род моей деятельности — решение бизнесовых задач в среде сурового энтерпрайза на java, а по своей натуре хочется узнавать новое и интересное, для поддержки навыков, обучаться новому и не стоять на месте (этакий линейный->делец). И по этой причине давно пал взгляд на docker.

И вот на текущем проекте местами начал внедрять практики использования docker в среде разработчиков и тестировщиков. А где появляется docker, появляются контейнеры и об этих контейнерах хочется знать все от CPU до Network I/O. Для всего этого сначала пользовался стандартной связкой docker ps и docker stats. Были некоторые неудобства из-за того что это две отдельные утилиты, но в целом было сонно, пока не наткнулся на ctop. Думаю многим кто использует docker он знаком.

Проект

Go, go, go… Первые впечатления - 1

Запустив 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. Буду рад предложениям как реализовать считывание метрик контейнеров с удаленных нод.

Автор: Александр Козленков

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js