В прошлом году у меня была возможность работать с множеством крутых инструментов и технологий. Пару из них были иди , вялый и докер . Мы в значительной степени используем slack для всех наших коммуникаций и постепенно добавляем все больше и больше интеграций в slack для получения информации от различных инструментов сборки и выполнения. В нашей среде у нас есть несколько различных сред докеров, и в настоящее время либо войдите на хост-компьютер, чтобы проверить состояние докера, либо используйте docker-u i (который мы взломали для работы с несколькими средами докеров). Несмотря на то, что docker-ui отлично работает и предоставляет отличный способ получить представление о том, что происходит в конкретном демоне docker, он не очень легкий и в некоторых сценариях требует много кликов, чтобы получить информацию, которую разработчик находясь в поиске.
В качестве альтернативы разработчики могут просто использовать интерфейс Docker API, но для этого потребуется открыть много портов брандмауэра. Поэтому мы начали изучать использование (или создание) интеграции Slack, которую мы могли бы использовать для запроса наших сред докера непосредственно из Slack. Таким образом, разработчикам не нужно будет использовать новый инструмент, они уже почти все время открыты и могут быстро получить статус определенного контейнера докера (и, если позволит время, мы также добавим интеграцию Jenkins для развертываний в различные среды разработки от слабины).
Если ваша команда еще не использует Slack, вы можете начать бесплатно и использовать все интересные возможности Slack. Я работал в командах из 30+ человек, которые использовали одну и ту же бесплатную версию Slack, так что нет смысла не использовать Slack. Так что достаточно слабого продвижения, давайте начнем.
Отправка команд из слабины
Slack предоставляет несколько различных способов интеграции с внешними системами. В этом сценарии мы будем использовать « Командную черту ». С помощью команд косой черты мы можем просто ввести нашу команду в Slack, и Slack отправит сообщение POST с приложением типа контента / x-www-form-urlencoded в сконфигурированную конечную точку, которая содержит такие данные:
01
02
03
04
05
06
07
08
09
10
|
token=gIkuvaNzQIHg97ATvDxqgjtO team_id=T0001 team_domain=example channel_id=C2147483705 channel_name=test user_id=U2147483697 user_name=Steve command=/weather text= 94070 response_url=https: //hooks.slack.com/commands/1234/5678 |
Теперь мы можем либо ответить напрямую, либо отправить ответ на предоставленный response_url. В этой статье мы просто ответим напрямую (что необходимо сделать в течение 3000 мс). Итак, во-первых, давайте настроим slack для отправки определенной команды:
В этом случае всякий раз, когда мы набираем / docker, команда отправляется на нашу настроенную конечную точку. Например, если мы введем «/ docker CI ps», мы получим обзор всех изображений, работающих в среде CI:
В следующих двух разделах мы покажем вам, как реализовать такой прокси с помощью go-lang.
Создание прокси-сервера
Весь код отсюда можно найти на моем github ( https://github.com/josdirksen/slack-proxy ), и если вы просто хотите запустить его, просто выполните следующие простые шаги:
1
2
3
4
5
6
7
8
9
|
$ git clone https: //github.com/josdirksen/slack-proxy $ cd slack-proxy $ export GOPATH=`pwd` $ go get github.com/fsouza/go-dockerclient $ go build src/github.com/ $ go build src/github.com/josdirksen/slackproxy/slack-proxy.go # Make sure to edit the config.json to point to the correct docker location and keys/certificates $ ./slack-proxy -config resources/config.json {5cLHiZjpWaRDb0fP6ka02XCR {[{local tcp: //192.168.99.100:2376 true /Users/jos/.docker/machine/machines/eris ca.pem cert.pem key.pem}]}} |
Давайте посмотрим на код. Первый файл, который мы рассмотрим, это основной файл slack-proxy.go:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
package main import ( "flag" "github.com/josdirksen/slackproxy/config" "github.com/josdirksen/slackproxy/handlers" "log" "net/http" ) // setup the listener, with a config passed in. func GetConfigListener(config *config.Configuration) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, req *http.Request) { // get the contents from the request body, convert it to a command req.ParseForm() cmdToExecute := handlers.ParseInput(req.Form) // now check whether the token is valid if config.Token == cmdToExecute.Token { // execute the command handler := handlers.GetHandler( "docker" , config) handler.HandleCommand(cmdToExecute, w) } else { w.WriteHeader( 406 ) } } } func StartListening(config *config.Configuration) { http.HandleFunc( "/handleSlackCommand" , GetConfigListener(config)) err := http.ListenAndServe( ":9000" , nil) if err != nil { log.Fatal( "ListenAndServe: " , err) } } func main() { var configLocation = flag.String( "config" , "config.json" , "specify the config file" ) flag.Parse() // first parse the config config.ParseConfig(*configLocation) // setup the handler that listens to 9000 StartListening(config.GetConfig()) } |
Как вы можете видеть здесь, мы не так много делаем. Главное, что мы делаем, это анализируем конфигурацию и настраиваем прослушиватель HTTP на порт 9000. Поэтому всякий раз, когда мы получаем запрос на порт 9000, указанный обработчик, возвращаемый GetConfigListener, вызывается:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
// setup the listener, with a config passed in. func GetConfigListener(config *config.Configuration) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, req *http.Request) { // get the contents from the request body, convert it to a command req.ParseForm() cmdToExecute := handlers.ParseInput(req.Form) // now check whether the token is valid if config.Token == cmdToExecute.Token { // execute the command handler := handlers.GetHandler( "docker" , config) handler.HandleCommand(cmdToExecute, w) } else { w.WriteHeader( 406 ) } } } |
Сам обработчик тоже не так уж и много делает. Он просто передает команду, которую необходимо выполнить, обработчику, указанному «docker». Обратите внимание, что на этом этапе мы поддерживаем только команды «docker». Но мы могли бы легко использовать эту настройку для обработки других команд. Подробнее об этом в следующей статье.
Обрабатывать команды
Итак, давайте посмотрим поближе на функцию handler.HandleCommand (которую вы можете найти в файле dockerCommandHandlers.go:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
|
package handlers import ( "io" "net/http" "fmt" "github.com/fsouza/go-dockerclient" "bytes" "time" "github.com/josdirksen/slackproxy/config" "log" "os" ) type DockerHandler struct { config *config.Configuration } func NewDockerHandler(configuration *config.Configuration) *DockerHandler { p := new (DockerHandler) p.config = configuration return p } func (dh DockerHandler) HandleCommand(cmdToExecute *Command, w http.ResponseWriter) { client := setupDockerClient(cmdToExecute.Environment) switch cmdToExecute.SlackCommand { case "ps" : handlePsCommand(client, w) case "imgs" : handleListImagesCommand(client, w) } } func handleListImagesCommand(client *docker.Client, w http.ResponseWriter) { images, _ := client.ListImages(docker.ListImagesOptions{All: false }) for _, img := range images { fmt.Println( "ID: " , img.ID) fmt.Println( "RepoTags: " , img.RepoTags) fmt.Println( "Created: " , img.Created) fmt.Println( "Size: " , img.Size) fmt.Println( "VirtualSize: " , img.VirtualSize) fmt.Println( "ParentId: " , img.ParentID) } } func handlePsCommand(client *docker.Client, w http.ResponseWriter) { containers, _ := client.ListContainers(docker.ListContainersOptions{All: false }) var buffer bytes.Buffer for _, container := range containers { buffer.WriteString(fmt.Sprintf( "ID: %s\n" , container.ID)) buffer.WriteString(fmt.Sprintf( "Command: %s\n" , container.Command)) buffer.WriteString(fmt.Sprintf( "Created: %s\n" , time.Unix(container.Created, 0 ))) buffer.WriteString(fmt.Sprintf( "Image: %s\n" , container.Image)) buffer.WriteString(fmt.Sprintf( "Status: %s\n" , container.Status)) buffer.WriteString(fmt.Sprintf( "Names: %s\n" , container.Names)) if (len(container.Ports) > 0 ) { buffer.WriteString( "Ports: \n" ) for _, port := range container.Ports { buffer.WriteString(fmt.Sprintf( "\t type: %s IP: %s private: %d public: %d\n" , port.Type, port.IP, port.PrivatePort, port.PublicPort)) } } buffer.WriteString(fmt.Sprint( "\n" )) } io.WriteString(w, buffer.String()) } func setupDockerClient(env string) *docker.Client { // first get the environment from the config cfg, err := config.GetDockerEnvironmentConfig(env) if (err != nil) { println(err) log.Fatal( "Can't parse config, exiting" ) os.Exit( 1 ) } if (cfg.Tls) { endpoint := cfg.Host path := cfg.Path ca := fmt.Sprintf( "%s/%s" , path, cfg.Ca) cert := fmt.Sprintf( "%s/%s" , path, cfg.Cert) key := fmt.Sprintf( "%s/%s" , path, cfg.Key) client,_ := docker.NewTLSClient(endpoint, cert, key, ca) return client } else { client,_ := docker.NewClient(cfg.Host) return client } } |
Много кода, но главное на что стоит обратить внимание — это функция HandleCommand. Здесь мы просто создаем новый клиент Docker (см. Код выше) и используем простой переключатель, чтобы определить, какую функцию вызывать.
1
2
3
4
5
6
7
8
|
func (dh DockerHandler) HandleCommand(cmdToExecute *Command, w http.ResponseWriter) { client := setupDockerClient(cmdToExecute.Environment) switch cmdToExecute.SlackCommand { case "ps" : handlePsCommand(client, w) case "imgs" : handleListImagesCommand(client, w) } } |
Например, если вы посмотрите немного ближе к handlePsCommand, вы увидите, что мы просто конвертируем ответ из функции client.ListContainers и записываем его в http.ResponseWriter.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
func handlePsCommand(client *docker.Client, w http.ResponseWriter) { containers, _ := client.ListContainers(docker.ListContainersOptions{All: false }) var buffer bytes.Buffer for _, container := range containers { buffer.WriteString(fmt.Sprintf( "ID: %s\n" , container.ID)) buffer.WriteString(fmt.Sprintf( "Command: %s\n" , container.Command)) buffer.WriteString(fmt.Sprintf( "Created: %s\n" , time.Unix(container.Created, 0 ))) buffer.WriteString(fmt.Sprintf( "Image: %s\n" , container.Image)) buffer.WriteString(fmt.Sprintf( "Status: %s\n" , container.Status)) buffer.WriteString(fmt.Sprintf( "Names: %s\n" , container.Names)) if (len(container.Ports) > 0 ) { buffer.WriteString( "Ports: \n" ) for _, port := range container.Ports { buffer.WriteString(fmt.Sprintf( "\t type: %s IP: %s private: %d public: %d\n" , port.Type, port.IP, port.PrivatePort, port.PublicPort)) } } buffer.WriteString(fmt.Sprint( "\n" )) } io.WriteString(w, buffer.String()) } |
# Запуск и тестирование кода
Теперь мы можем построить и запустить код:
1
2
3
4
5
6
7
8
9
|
$ git clone https: //github.com/josdirksen/slack-proxy $ cd slack-proxy $ export GOPATH=`pwd` $ go get github.com/fsouza/go-dockerclient $ go build src/github.com/ $ go build src/github.com/josdirksen/slackproxy/slack-proxy.go # Make sure to edit the config.json to point to the correct docker location and keys/certificates $ ./slack-proxy -config resources/config.json {5cLHiZjpWaRDb0fP6ka02XCR {[{local tcp: //192.168.99.100:2376 true /Users/jos/.docker/machine/machines/eris ca.pem cert.pem key.pem}]}} |
И чтобы проверить это, мы используем curl для отправки запроса. Запрос, который мы тестируем, выглядит так:
1
|
$ curl 'http://localhost:9000/handleSlackCommand' -H 'Content-Type: application/x-www-form-urlencoded' --data 'token=5cLHiZjpWaRDb0fP6ka02XCR&team_id=T0001&team_domain=example&channel_id=C2147483705&channel_name=test&user_id=U2147483697&user_name=Steve,nd=/docker&text=local+ps' |
Который возвращает информацию, как это:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
ID: 669265a1343601468e71fbe65dec0251595f6c043ceeb5748f6736b28fd5da39 Command: /bin/start -server -advertise 192.168 . 99.100 -bootstrap-expect 1 Created: 2016 - 01 - 06 08 : 05 : 53 + 0100 CET Image: docker-register.equeris.com/consul:eris-local Status: Up 3 minutes Names: [/consul] Ports: type: tcp IP: 192.168 . 99.100 private : 8400 public : 8400 type: tcp IP: 192.168 . 99.100 private : 8302 public : 8302 type: tcp IP: 192.168 . 99.100 private : 8301 public : 8301 type: udp IP: 192.168 . 99.100 private : 53 public : 53 type: tcp IP: private : 53 public : 0 type: tcp IP: 192.168 . 99.100 private : 8500 public : 8500 type: udp IP: 192.168 . 99.100 private : 8301 public : 8301 type: tcp IP: 192.168 . 99.100 private : 8300 public : 8300 type: udp IP: 192.168 . 99.100 private : 8302 public : 8302 ID: 682f47f97fced8725d228a68a4bdf5486bf0d3e247f6864331e4f6b413b92f65 Command: /find-ip-add-to-consul.sh -Dcassandra.load_ring_state= false Created: 2015 - 11 - 24 13 : 29 : 38 + 0100 CET Image: docker-register.equeris.com/cassandra:eris Status: Up 3 minutes Names: [/cassandra-eris] Ports: type: tcp IP: 192.168 . 99.100 private : 9160 public : 9160 type: tcp IP: 192.168 . 99.100 private : 9042 public : 9042 type: tcp IP: private : 7199 public : 0 type: tcp IP: private : 7001 public : 0 type: tcp IP: 192.168 . 99.100 private : 7000 public : 7000 |
Вот именно для этой статьи. Обратите внимание, что мы упустили много вещей, мы только реализовали команду ps, не проводя много проверки или обработки ошибок. В последующих статьях мы подробнее рассмотрим добавление дополнительных функций к этому прокси.
Ссылка: | Создайте прокси Slack Docker в Go — часть 1 от нашего партнера JCG Йоса Дирксена из блога Smart Java . |