Begin adding system container log support to diagnostics

Signed-off-by: Nathan LeClaire <nathan.leclaire@gmail.com>
This commit is contained in:
Nathan LeClaire 2017-01-09 18:08:00 -08:00
parent 495dcf63c5
commit 69468bf42f
3 changed files with 115 additions and 25 deletions

View File

@ -15,7 +15,8 @@ import (
)
const (
defaultCommandTimeout = 5 * time.Second
defaultCaptureTimeout = 5 * time.Second
defaultCommandTimeout = defaultCaptureTimeout
// Might eventually have some pretty long (~30s) traces in here, so 35
// seconds seems reasonable.

View File

@ -19,7 +19,7 @@ import (
)
var (
errDockerPingNotOK = errors.New("Docker /_ping did not return OK")
errDockerRespNotOK = errors.New("Docker API call did not return OK")
)
const (
@ -39,12 +39,38 @@ func init() {
for _, c := range commonCmdCaptures {
cloudCaptures = append(cloudCaptures, c)
}
cloudCaptures = append(cloudCaptures, SystemContainerCapturer{})
}
// HTTPDiagnosticListener sets a health check and optional diagnostic endpoint
// for cloud editions.
type HTTPDiagnosticListener struct{}
func dockerHTTPGet(ctx context.Context, url string) (*http.Response, error) {
client := &http.Client{
Transport: &UnixSocketRoundTripper{},
}
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
resp, err := client.Do(req)
if err != nil {
return resp, err
}
if resp.StatusCode != http.StatusOK {
return resp, errDockerRespNotOK
}
return resp, err
}
// UnixSocketRoundTripper provides a way to make HTTP request to Docker socket
// directly.
type UnixSocketRoundTripper struct{}
@ -63,35 +89,14 @@ func (u UnixSocketRoundTripper) RoundTrip(req *http.Request) (*http.Response, er
// Listen starts the HTTPDiagnosticListener and sets up handlers for its endpoints
func (h HTTPDiagnosticListener) Listen() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
client := &http.Client{
Transport: &UnixSocketRoundTripper{},
}
req, err := http.NewRequest(http.MethodGet, "/_ping", nil)
if err != nil {
http.Error(w, "Error creating HTTP request to talk to Docker", http.StatusInternalServerError)
return
}
ctx, cancel := context.WithTimeout(context.Background(), healthcheckTimeout)
defer cancel()
req = req.WithContext(ctx)
errCh := make(chan error)
go func() {
resp, err := client.Do(req)
if err != nil {
errCh <- err
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
errCh <- errDockerPingNotOK
return
}
errCh <- nil
_, err := dockerHTTPGet(ctx, "/_ping")
errCh <- err
}()
select {

View File

@ -0,0 +1,84 @@
package main
import (
"archive/tar"
"bytes"
"context"
"encoding/json"
"io/ioutil"
"log"
)
const (
systemLogDir = "editionslogs"
systemContainerLabel = "system"
)
// SystemContainerCapturer gets the logs from containers which are run
// specifically for Docker Editions.
type SystemContainerCapturer struct{}
// Capture writes output from a CommandCapturer to a tar archive
func (s SystemContainerCapturer) Capture(parentCtx context.Context, w *tar.Writer) {
done := make(chan struct{})
ctx, cancel := context.WithTimeout(context.Background(), defaultCaptureTimeout)
defer cancel()
go func() {
resp, err := dockerHTTPGet(ctx, "/containers/json?all=1&label="+systemContainerLabel)
if err != nil {
log.Println("ERROR:", err)
return
}
defer resp.Body.Close()
names := []struct {
ID string `json:"id"`
Names []string
}{}
if err := json.NewDecoder(resp.Body).Decode(&names); err != nil {
log.Println("ERROR:", err)
return
}
for _, c := range names {
resp, err := dockerHTTPGet(ctx, "/containers/"+c.ID+"/logs?stderr=1&stdout=1&timestamps=1")
if err != nil {
log.Println("ERROR:", err)
continue
}
defer resp.Body.Close()
logLines, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Println("ERROR:", err)
continue
}
// Docker API returns a Names array where the names are
// like this: ["/foobar", "/quux"]
//
// Use the first one from it.
//
// Additionally, the slash from it helps delimit a path
// separator in the tar archive.
//
// TODO(nathanleclaire): This seems fragile, but I'm
// not sure what approach would be much better.
tarWrite(w, bytes.NewBuffer(logLines), systemLogDir+c.Names[0])
}
done <- struct{}{}
}()
select {
case <-ctx.Done():
log.Println("System container log capture error", ctx.Err())
case <-done:
log.Println("System container log capture finished")
}
}