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

Комментарии (53)


  1. mrobespierre
    29.08.2017 09:18
    +6

    всё это здорово конечно, но вы несколько раз используете слово «класс», которых в Go нет, и смешиваете в кучу packages (суть модули) и interfaces (тип данных, как byte или string), из-за чего я не всё понял


    1. sah4ez32 Автор
      29.08.2017 09:23
      -3

      Использование словам "класс" — это наследство от java, так как первое знакомство, он местами проскакивает. На самом деле да, тут используются структуры, что непривычно по началу.


      1. SirEdvin
        29.08.2017 10:25
        +1

        Проблема в том, что в go привычных классов и вместо наследывания предлагают встраивание (например, тут и тут). Это позволяет некоторым, например мне, считать, что Golang больше крутой мейнстримный процедурный язык, который был нужен многим, потому что у них был только C, но подходящий для узкого круга задач.


        А отсутствие шаблонных типов превращает работу там, где они нужны в мешанину из interface{}. Хотя вот в go 2.0 вроде они будут.


        1. Emacs232
          31.08.2017 18:24
          -3

          спасибо, Ваше мнение очень важно для нас


  1. sah4ez32 Автор
    29.08.2017 09:22

    del


  1. SirEdvin
    29.08.2017 10:21
    +1

    (хотя логично, docker на нем написан)
    А docker-compose написал на python, хотя причин я не понимаю до сих пор)


    1. sah4ez32 Автор
      29.08.2017 10:27

      Собственно первая надежда что ctop на python оттуда и зарадилась.


    1. jarosite
      29.08.2017 15:34

      можно сказать что так исторически сложилось, потому что он основан на исходниках фига (http://www.fig.sh/), который до этого назывался plum.


  1. laughman
    29.08.2017 10:43

    Что лучше, Go или D? В качестве некстгена плюсов? Без гуя и с ним? В треугольнике винда-линукс-андроид?


    1. SirEdvin
      29.08.2017 10:46
      +2

      В треугольнике винда-линукс-андроид?

      Java. Вы, конечно, можете попробовать делать это на Go, но я бы не советовал. И они пока тоже.


      1. nerumb
        29.08.2017 10:52

        Или можно еще попробовать Kotlin.


    1. beduin01
      29.08.2017 11:15
      +2

      >Что лучше, Go или D?
      Вот наглядный пример.


      1. calg0n
        30.08.2017 11:46
        -3

        Как ни странно, слева понятно что происходит, а справа какая-то магия.


        1. DarkEld3r
          30.08.2017 13:02
          +1

          Ну я вот D не знаю совершенно, а код справа вполне понятен.


          1. calg0n
            30.08.2017 14:03
            -1

            Вполне, ещё не значит что понятен. Go многое впитал в себя от Си и это видно по коду. D судя по тому что я вижу позаимствовал магию от Ruby и с наскока данный код не будет понятен тем кто с Ruby не сталкивался. Так что тут 2 языка со разными корнями. Но Си с как по мне более прямолинеен и последователен, как собственно и Go.


            1. DarkEld3r
              30.08.2017 14:49
              +1

              Вот только Ruby я знаю ещё меньше чем D, а код всё равно понятен. (:


              Так-то соглашусь, что если у человека есть опыт исключительно С, то да, Go будет очевидно проще. Но как по мне, изучать свой инструмент всё равно надо, а дальше можно бесконечно спорить и кривой обучения и пороге входа.


            1. jacob1237
              30.08.2017 15:39
              +1

              Не знаю причем тут магия, но то что Вы видите в строке 7, называется Uniform Function Call Syntax, который позволяет писать функции не по-отдельности, а через точку, в таком случае первый аргумент функции будет являться тем объектом, к которому Вы эту функцию присовокупили (иными словами foo(a); это аналог a.foo();): https://tour.dlang.org/tour/ru/gems/uniform-function-call-syntax-ufcs


              И эта особенность как раз сильно сокращает время на чтение и понимание кода. А также дает некоторые интересные возможности.


              1. calg0n
                30.08.2017 16:10
                -1

                Это конечно всё круто, но то что вы описываете и есть особенности языка, в Ruby тоже самое можно сделать насколько я знаю, и периодически сталкиваясь с Ruby, знаю не понаслышке о его магических способностях. Поэтому D и напомнил мне Ruby. По синтаксису Go можно сразу сказать что тут всё прямолинейно как в Си и для тех кто сталкивался с Си (я думаю те кто учился на IT специальностях по любому с ним сталкивались) легко освоит Go ибо там всё также просто и прямолинейно.


                1. sah4ez32 Автор
                  30.08.2017 16:14
                  -1

                  Хочу заметить, что даже для тех кто сталкивался с Java и Python, и не сталкивался с Си, Go показался достаточно дружелюбным по дизайну.


                  1. calg0n
                    30.08.2017 16:22
                    -1

                    Согласен :)


    1. Siemargl
      29.08.2017 12:26

      D не умеет Андроид пока и не является промышленным.
      Go не для GUI.


      1. beduin01
        29.08.2017 12:34

        D умеет Android через LDC «Full Android support, incl. emulated TLS»


        1. Siemargl
          29.08.2017 13:28

          Да, похоже работы в этом направлении к чему то привели — новинка LDC 2017г.
          Но выглядит пока не очень. JNI и чистый OpenGL. Ждем GUI-libs

          2. Нет, генерация кода html не является GUI


      1. pfihr
        29.08.2017 12:46

        А gui на вэб-формах html/css/js уже не gui? Пример реализации можно увидеть в syncthing.


      1. raydac
        29.08.2017 13:16

        сейчас GUI нередко через веб интерфейс делают, при таком Go справляется не хуже остальных имхо, еще большой плюс что есть приличное количество библиотек для организации консольного интерфейса и даже некоторые аналоги старого доброго Turbo Vision


    1. mrobespierre
      29.08.2017 12:30

      моё мнение такое: Go — язык нишевой, он хорош для создания небольших сервисов (демонов), ориентированных на работу с сетью (в. т.ч. и веб-приложений) и консольных клиентов к ним (или просто утилит). и в этой нише он «лучший» (да, я гофер, да я уже пробовал php, python, ruby и даже perl). в «треугольнике винда-линукс-андроид» лучше D (если вы уверены, что D — некстген C++, я, например, не уверен, и брал бы плюсы, при всех их минусах)


      1. raydac
        29.08.2017 13:17

        а чем он так сильно от Java отличается что на последней можно большое, а на Go нельзя? а то я может что то не заметил


        1. mrobespierre
          29.08.2017 16:26

          можно, можно большое, и я нигде не писал обратного, про java тоже не писал, я лишь отвечал человеку на вопрос

          Что лучше, Go или D?

          и подчеркнул то, в чём Go, на мой взгляд, хорош

          недавно здесь на Хабре была статья про веб-приложение, кажется, на asm, но «можно» != «хорош в чём-то»


      1. farcaller
        29.08.2017 15:46
        -1

        Но ведь куча крупного серверного софта написана на го?


        1. mrobespierre
          29.08.2017 16:19

          Да, они на Go, да «проекты большие», но если вы таки посмотрите на исходный код, то увидите, что 2 из 3 ваших примеров состоят из 10(!)+ небольших сервисов и клиентов к ним, хотя можно было обойтись и 2-мя. Concource — да, состоит из «всего» 4-х кусков, но это никак не опровергает мой тезис «Go хорош для небольших демонов».


          1. farcaller
            29.08.2017 16:23

            2 из 3 это k8s и docker? Docker раньше был одним процессом (плюс commandline клиент), его разделили из-за необходимости масштабирования компонентов независимо друг от друга. Рантайм отдельно, управление отдельно. Благодаря такому разделению в нем теперь есть такая вещь как live restart (перезапуск управляющего демона без перезапуска контейнеров). Звучит как хорошая причина для разделения?


            На тему k8s, я хотел бы услышать ваше мнение за счет чего можно сократить количество демонов. Но независимо от их числа, вы прямо так считаете тот же kubelet "небольшим"?


      1. pawlo16
        31.08.2017 13:49

        Большие сервисы тоже можно как показывает практика гигантов индустрии :)

        Для не слишком навороченных программ под винду Go вполне себе годится благодаря github.com/lxn/walk — качественная тонкая обёртка для стандартных win api контролов в декларативном стиле.

        я бы тоже взял С++, поскольку возится с не поддерживаемыми биндингами нет ни какого желания


    1. apro
      29.08.2017 12:45
      +3

      В качестве nextget плюсов по-моему пока только Rust выступает. Все-таки у обоих D и Go есть GC. Поэтому если вам не нужна zero cost abstraction, то стоит задуматься может подойдут не компилируемые в native языки, типа Java, C# и т.д.


      1. TargetSan
        29.08.2017 14:20

        Не-компилируемые хотят рантайм. Например, была задачка — собрать тонкий Node.JS сервис из толстой нативной либы и засунуть в докер. Подружить этот зоопарк, чтобы собирался одной кнопкой — нетривиально, хоть и решаемо. А потом корректно проставить всё в контейнер и вычистить мусор. Куда проще скрутить и запаковать монолитный бинарь.


    1. jacob1237
      30.08.2017 15:28

      В качестве "некстгена плюсов" однозначно D, но к сожалению в текущей реализации на 100% плюсы он заменить не может, если конечно не писать без использования сборщика мусора. Хоть это и возможно, но все равно без GC теряются дефолтные фишки языка такие ассоциативные массивы, делегаты и т.д. (в будущем возможно будут какие-то изменения касательно отвязки библиотеки от GC). В принципе и для этих примитивов есть замена без GC, если понадобится.


      Сейчас D напоминает скорее нечто среднее между C# и Java, но только не требующее никаких виртуальных машин и рантаймов, т.к. исполняемый файл можно статически слинковать со всеми необходимыми библиотеками.


      Для большинства прикладных задач сборщик мусора ничем не мешает и даже очень сильно помогает.
      Ну и плюс если хочется совсем низкий уровень или откат к C, есть режим Better-C.


      Так что если не пишите какие-то супербыстрые сетевые сервисы, то из этих двух однозначно D (хотя для сетевых сервисов в D есть фреймворк vibe.d)


  1. beduin01
    29.08.2017 11:13
    +5

    На фоне Go даже Python выглядит более совершенным. По факту в Go простоту довели до примитивизма и похерили все наработки в области разработки языков программирования за последние 50 лет.

    В итоге язык больше обрубок какой-то напоминает. Про серьезные проекты на нем читать вообще смешно. Ну да. Запилили на нем REST сервис который JSON отдает. Замечательно! Просто прорыв. А что дальше? Язык не поддерживает элементарные обобщения и обработку данных на нем вести практически невозможно. В итоге кроме как JSON-ом плеваться ничего другого не остается.


    1. SirEdvin
      29.08.2017 12:28
      +4

      В этом и смысл. Поэтому большинство любителей Golang — автоматически становятся любителями микросервисов, потому что большие монолиты на нем не напишешь. А микросервисы, без всего этого ООП и боли отлично пишутся.
      Раньше, если вам нужен был микросервис, у вас был только Python или C++, не очень крутой выбор. А тут теперь и Golang докинули.


      1. 0xd34df00d
        30.08.2017 20:59
        +1

        Раньше, если вам нужен был микросервис, у вас был только Python или C++, не очень крутой выбор.

        Я микросервисы на хаскеле пишу :(


    1. mrobespierre
      29.08.2017 12:39

      обработку данных на нем вести практически невозможно

      а можно примеры, пожалуйста, не могу представить такие данные, что не обработать в Go
      кроме как JSON-ом плеваться ничего другого не остается

      так сейчас время такое и большинство бизнес-процессов вокруг этого построено, разве нет?


      1. beduin01
        29.08.2017 12:43

        Да к примеру простое суммирование превращается в ад. Аж 40 строк. Нафига спрашивается? Со всеми остальными данными тоже самое. Приходится писать простыни. Код ради кода.

        >так сейчас время такое и большинство бизнес-процессов вокруг этого построено, разве нет?
        Сейчас время BigData и очень часто на сервере нужно что-то посчитать и обработать прежде чем отдать.


        1. pfihr
          29.08.2017 12:51
          -1

          Я могу такой код еще 6 раз скопировать, и он будет еще больше казаться. Сравненип кода абсолютно неверное, нет использования интерфейсов, например.


          1. 0xd34df00d
            30.08.2017 21:00
            +1

            А как это будет выглядеть с интерфейсами?


        1. IncorrecTSW
          29.08.2017 12:52
          +2

          Пример на скрине честно говоря из пальца высосан. int64sum очевидно покрывает все описанные там кейсы, всю ту простыню можно свести в несколько строк.


        1. ertaquo
          29.08.2017 12:52

          Слева у вас код дублируется аж четыре раза. Без этого ненужного дублирования код сокращается ровно до 10 строк, что не так уж и много.


        1. pfihr
          29.08.2017 13:02
          -3

          x:=0
          for _,y:=range []int{1,2,3,4,5} {x+=y}
          fnt.Println(x)

          В одну строку


          1. beduin01
            29.08.2017 13:26
            +3

            И для каждого отдельного типа придется писать отдельную функцию.


            1. pfihr
              29.08.2017 16:37
              -3

              нет, интерфейсы и рефлексию никто не отменял


              1. TargetSan
                29.08.2017 16:58
                +4

                Не отменял. Но это то же, что было в Java до 1.5.


        1. mrobespierre
          29.08.2017 13:36

          Да к примеру простое суммирование превращается в ад. Аж 40 строк. Нафига спрашивается?

          автор того сниппета с сайта dlang очевидно болен, не думаю, что здесь есть что обсуждать
          Сейчас время BigData и очень часто на сервере нужно что-то посчитать и обработать прежде чем отдать

          вот в этом гошечке просто нет равных ибо идеоматичный код:
          1. автоматически масштабируется на любое количество доступных ядер (будь их хоть 2, хоть 64)
          2. практически не блокируется
          3. в принципе быстро работает: даже при одном потоке Go, в среднем, быстрее Java и NodeJS (популярный нынче выбор для бэкендов) и во много раз (до 10) быстрее Python3.
          4. имеет небольшое и прогнозируемое потребление памяти


          1. imanushin
            29.08.2017 14:21

            в принципе быстро работает: даже при одном потоке Go, в среднем, быстрее Java и NodeJS (популярный нынче выбор для бэкендов) и во много раз (до 10) быстрее Python3.

            Есть сайт для сравнения скорости разных платформ: https://www.techempower.com/benchmarks/#section=data-r14&hw=ph&test=json
            Примечательно, что если кто-то считает, что решение неэффективно — он всегда может это исправить.


            Итак, судя по этому сравнению, на всех типичных задачах Go медленнее, чем Java или C++ .


            Отсюда вопрос: а на каких бенчмарках Python оказался медленнее в 10 раз, чем Go? На каких бенчамарках Java оказалась медленнее, чем Go?



    1. calg0n
      30.08.2017 11:49
      +1

      Упрощение, разделение и микросервисы — наше всё! Для этого го и предназначен.


  1. raydac
    29.08.2017 12:54
    +1

    как язык Go достаточно банален, никаких новых концепций особо незаметно, но там есть две плюшки из-за которых данная платформа мне импонирует
    1. на выходе исполняемый файл не требующий с потребителя «скачать еще сто мегабайт неведомой фигни и проинсталлировать обеспечив что бы уже не стояла неведомая фигня в путях»
    2. кросс-компиляция осуществляется без плясок с бубном, всеголишь настройкой пары параметров и под линуксом вполне билдятся версии под винду и мак