Re-implement ContainerInspect call in Wings to use more performant json encoder (#119)
* First pass at re-implementing the Docker inspect call to use more efficient json parser * Improve logic
This commit is contained in:
parent
34c0db9dff
commit
34ecf20467
|
@ -75,6 +75,8 @@ type DockerConfiguration struct {
|
||||||
// Overhead controls the memory overhead given to all containers to circumvent certain
|
// Overhead controls the memory overhead given to all containers to circumvent certain
|
||||||
// software such as the JVM not staying below the maximum memory limit.
|
// software such as the JVM not staying below the maximum memory limit.
|
||||||
Overhead Overhead `json:"overhead" yaml:"overhead"`
|
Overhead Overhead `json:"overhead" yaml:"overhead"`
|
||||||
|
|
||||||
|
UsePerformantInspect bool `default:"true" json:"use_performant_inspect" yaml:"use_performant_inspect"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegistryConfiguration defines the authentication credentials for a given
|
// RegistryConfiguration defines the authentication credentials for a given
|
||||||
|
|
116
environment/docker/api.go
Normal file
116
environment/docker/api.go
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
package docker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"emperror.dev/errors"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/versions"
|
||||||
|
"github.com/docker/docker/client"
|
||||||
|
"github.com/docker/docker/errdefs"
|
||||||
|
"github.com/goccy/go-json"
|
||||||
|
"github.com/pterodactyl/wings/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
o sync.Once
|
||||||
|
cli cliSettings
|
||||||
|
fastEnabled bool
|
||||||
|
)
|
||||||
|
|
||||||
|
type cliSettings struct {
|
||||||
|
enabled bool
|
||||||
|
proto string
|
||||||
|
host string
|
||||||
|
scheme string
|
||||||
|
version string
|
||||||
|
}
|
||||||
|
|
||||||
|
func configure(c *client.Client) {
|
||||||
|
o.Do(func() {
|
||||||
|
fastEnabled = config.Get().Docker.UsePerformantInspect
|
||||||
|
|
||||||
|
r := reflect.ValueOf(c).Elem()
|
||||||
|
cli.proto = r.FieldByName("proto").String()
|
||||||
|
cli.host = r.FieldByName("addr").String()
|
||||||
|
cli.scheme = r.FieldByName("scheme").String()
|
||||||
|
cli.version = r.FieldByName("version").String()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainerInspect is a rough equivalent of Docker's client.ContainerInspect()
|
||||||
|
// but re-written to use a more performant JSON decoder. This is important since
|
||||||
|
// a large number of requests to this endpoint are spawned by Wings, and the
|
||||||
|
// standard "encoding/json" shows its performance woes badly even with single
|
||||||
|
// containers running.
|
||||||
|
func (e *Environment) ContainerInspect(ctx context.Context) (types.ContainerJSON, error) {
|
||||||
|
configure(e.client)
|
||||||
|
|
||||||
|
// Support feature flagging of this functionality so that if something goes
|
||||||
|
// wrong for now it is easy enough for people to switch back to the older method
|
||||||
|
// of fetching stats.
|
||||||
|
if !fastEnabled {
|
||||||
|
return e.client.ContainerInspect(ctx, e.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
var st types.ContainerJSON
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "/containers/"+e.Id+"/json", nil)
|
||||||
|
if err != nil {
|
||||||
|
return st, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cli.proto == "unix" || cli.proto == "npipe" {
|
||||||
|
req.Host = "docker"
|
||||||
|
}
|
||||||
|
|
||||||
|
req.URL.Host = cli.host
|
||||||
|
req.URL.Scheme = cli.scheme
|
||||||
|
|
||||||
|
res, err := e.client.HTTPClient().Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return st, errdefs.FromStatusCode(err, res.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := io.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return st, errors.Wrap(err, "failed to read response body from Docker")
|
||||||
|
}
|
||||||
|
if err := parseErrorFromResponse(res, body); err != nil {
|
||||||
|
return st, errdefs.FromStatusCode(err, res.StatusCode)
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(body, &st); err != nil {
|
||||||
|
return st, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
return st, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseErrorFromResponse is a re-implementation of Docker's
|
||||||
|
// client.checkResponseErr() function.
|
||||||
|
func parseErrorFromResponse(res *http.Response, body []byte) error {
|
||||||
|
if res.StatusCode >= 200 && res.StatusCode < 400 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var ct string
|
||||||
|
if res.Header != nil {
|
||||||
|
ct = res.Header.Get("Content-Type")
|
||||||
|
}
|
||||||
|
|
||||||
|
var emsg string
|
||||||
|
if (cli.version == "" || versions.GreaterThan(cli.version, "1.23")) && ct == "application/json" {
|
||||||
|
var errResp types.ErrorResponse
|
||||||
|
if err := json.Unmarshal(body, &errResp); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
emsg = strings.TrimSpace(errResp.Message)
|
||||||
|
} else {
|
||||||
|
emsg = strings.TrimSpace(string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.Wrap(errors.New(emsg), "Error response from daemon")
|
||||||
|
}
|
|
@ -118,7 +118,7 @@ func (e *Environment) InSituUpdate() error {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if _, err := e.client.ContainerInspect(ctx, e.Id); err != nil {
|
if _, err := e.ContainerInspect(ctx); err != nil {
|
||||||
// If the container doesn't exist for some reason there really isn't anything
|
// If the container doesn't exist for some reason there really isn't anything
|
||||||
// we can do to fix that in this process (it doesn't make sense at least). In those
|
// we can do to fix that in this process (it doesn't make sense at least). In those
|
||||||
// cases just return without doing anything since we still want to save the configuration
|
// cases just return without doing anything since we still want to save the configuration
|
||||||
|
@ -150,7 +150,7 @@ func (e *Environment) Create() error {
|
||||||
// If the container already exists don't hit the user with an error, just return
|
// If the container already exists don't hit the user with an error, just return
|
||||||
// the current information about it which is what we would do when creating the
|
// the current information about it which is what we would do when creating the
|
||||||
// container anyways.
|
// container anyways.
|
||||||
if _, err := e.client.ContainerInspect(context.Background(), e.Id); err == nil {
|
if _, err := e.ContainerInspect(context.Background()); err == nil {
|
||||||
return nil
|
return nil
|
||||||
} else if !client.IsErrNotFound(err) {
|
} else if !client.IsErrNotFound(err) {
|
||||||
return errors.Wrap(err, "environment/docker: failed to inspect container")
|
return errors.Wrap(err, "environment/docker: failed to inspect container")
|
||||||
|
|
|
@ -10,7 +10,6 @@ import (
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
|
|
||||||
"github.com/pterodactyl/wings/environment"
|
"github.com/pterodactyl/wings/environment"
|
||||||
"github.com/pterodactyl/wings/events"
|
"github.com/pterodactyl/wings/events"
|
||||||
"github.com/pterodactyl/wings/remote"
|
"github.com/pterodactyl/wings/remote"
|
||||||
|
@ -116,7 +115,7 @@ func (e *Environment) Events() *events.Bus {
|
||||||
// will work fine when using the container name as the lookup parameter in addition to the longer
|
// will work fine when using the container name as the lookup parameter in addition to the longer
|
||||||
// ID auto-assigned when the container is created.
|
// ID auto-assigned when the container is created.
|
||||||
func (e *Environment) Exists() (bool, error) {
|
func (e *Environment) Exists() (bool, error) {
|
||||||
_, err := e.client.ContainerInspect(context.Background(), e.Id)
|
_, err := e.ContainerInspect(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If this error is because the container instance wasn't found via Docker we
|
// If this error is because the container instance wasn't found via Docker we
|
||||||
// can safely ignore the error and just return false.
|
// can safely ignore the error and just return false.
|
||||||
|
@ -140,7 +139,7 @@ func (e *Environment) Exists() (bool, error) {
|
||||||
//
|
//
|
||||||
// @see docker/client/errors.go
|
// @see docker/client/errors.go
|
||||||
func (e *Environment) IsRunning(ctx context.Context) (bool, error) {
|
func (e *Environment) IsRunning(ctx context.Context) (bool, error) {
|
||||||
c, err := e.client.ContainerInspect(ctx, e.Id)
|
c, err := e.ContainerInspect(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
@ -150,7 +149,7 @@ func (e *Environment) IsRunning(ctx context.Context) (bool, error) {
|
||||||
// Determine the container exit state and return the exit code and whether or not
|
// Determine the container exit state and return the exit code and whether or not
|
||||||
// the container was killed by the OOM killer.
|
// the container was killed by the OOM killer.
|
||||||
func (e *Environment) ExitState() (uint32, bool, error) {
|
func (e *Environment) ExitState() (uint32, bool, error) {
|
||||||
c, err := e.client.ContainerInspect(context.Background(), e.Id)
|
c, err := e.ContainerInspect(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// I'm not entirely sure how this can happen to be honest. I tried deleting a
|
// I'm not entirely sure how this can happen to be honest. I tried deleting a
|
||||||
// container _while_ a server was running and wings gracefully saw the crash and
|
// container _while_ a server was running and wings gracefully saw the crash and
|
||||||
|
|
|
@ -66,7 +66,7 @@ func (e *Environment) Start(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if c, err := e.client.ContainerInspect(ctx, e.Id); err != nil {
|
if c, err := e.ContainerInspect(ctx); err != nil {
|
||||||
// Do nothing if the container is not found, we just don't want to continue
|
// Do nothing if the container is not found, we just don't want to continue
|
||||||
// to the next block of code here. This check was inlined here to guard against
|
// to the next block of code here. This check was inlined here to guard against
|
||||||
// a nil-pointer when checking c.State below.
|
// a nil-pointer when checking c.State below.
|
||||||
|
@ -235,7 +235,7 @@ func (e *Environment) WaitForStop(seconds uint, terminate bool) error {
|
||||||
|
|
||||||
// Terminate forcefully terminates the container using the signal provided.
|
// Terminate forcefully terminates the container using the signal provided.
|
||||||
func (e *Environment) Terminate(signal os.Signal) error {
|
func (e *Environment) Terminate(signal os.Signal) error {
|
||||||
c, err := e.client.ContainerInspect(context.Background(), e.Id)
|
c, err := e.ContainerInspect(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Treat missing containers as an okay error state, means it is obviously
|
// Treat missing containers as an okay error state, means it is obviously
|
||||||
// already terminated at this point.
|
// already terminated at this point.
|
||||||
|
|
|
@ -16,7 +16,7 @@ import (
|
||||||
// Uptime returns the current uptime of the container in milliseconds. If the
|
// Uptime returns the current uptime of the container in milliseconds. If the
|
||||||
// container is not currently running this will return 0.
|
// container is not currently running this will return 0.
|
||||||
func (e *Environment) Uptime(ctx context.Context) (int64, error) {
|
func (e *Environment) Uptime(ctx context.Context) (int64, error) {
|
||||||
ins, err := e.client.ContainerInspect(ctx, e.Id)
|
ins, err := e.ContainerInspect(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, errors.Wrap(err, "environment: could not inspect container")
|
return 0, errors.Wrap(err, "environment: could not inspect container")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user