mirror of
https://github.com/linuxkit/linuxkit.git
synced 2025-07-23 02:51:55 +00:00
Begin adding system container log support to diagnostics
Signed-off-by: Nathan LeClaire <nathan.leclaire@gmail.com>
This commit is contained in:
parent
495dcf63c5
commit
69468bf42f
@ -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.
|
||||
|
@ -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 {
|
||||
|
84
alpine/packages/diagnostics/system_log_capture.go
Normal file
84
alpine/packages/diagnostics/system_log_capture.go
Normal 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×tamps=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")
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user