Compare commits
8 Commits
v1.0.0-bet
...
v1.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f6cd384d5 | ||
|
|
483b652087 | ||
|
|
a6645aa741 | ||
|
|
ffd7357a1c | ||
|
|
b36f0de337 | ||
|
|
b2cf222a3a | ||
|
|
ced8a5bcbd | ||
|
|
7bba1d4fd6 |
2
go.mod
2
go.mod
@@ -1,6 +1,6 @@
|
|||||||
module github.com/pterodactyl/wings
|
module github.com/pterodactyl/wings
|
||||||
|
|
||||||
go 1.12
|
go 1.13
|
||||||
|
|
||||||
// Uncomment this in development environments to make changes to the core SFTP
|
// Uncomment this in development environments to make changes to the core SFTP
|
||||||
// server software. This assumes you're using the official Pterodactyl Environment
|
// server software. This assumes you're using the official Pterodactyl Environment
|
||||||
|
|||||||
1
go.sum
1
go.sum
@@ -376,6 +376,7 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
|
|||||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20200417140056-c07e33ef3290 h1:NXNmtp0ToD36cui5IqWy95LC4Y6vT/4y3RnPxlQPinU=
|
golang.org/x/tools v0.0.0-20200417140056-c07e33ef3290 h1:NXNmtp0ToD36cui5IqWy95LC4Y6vT/4y3RnPxlQPinU=
|
||||||
golang.org/x/tools v0.0.0-20200417140056-c07e33ef3290/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
golang.org/x/tools v0.0.0-20200417140056-c07e33ef3290/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b h1:zSzQJAznWxAh9fZxiPy2FZo+ZZEYoYFYYDYdOrU7AaM=
|
||||||
golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
golang.org/x/tools/gopls v0.1.3/go.mod h1:vrCQzOKxvuiZLjCKSmbbov04oeBQQOb4VQqwYK2PWIY=
|
golang.org/x/tools/gopls v0.1.3/go.mod h1:vrCQzOKxvuiZLjCKSmbbov04oeBQQOb4VQqwYK2PWIY=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import (
|
|||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"io"
|
"io"
|
||||||
"math"
|
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -197,18 +196,33 @@ func (d *DockerEnvironment) Start() error {
|
|||||||
return &suspendedError{}
|
return &suspendedError{}
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err := d.Client.ContainerInspect(context.Background(), d.Server.Uuid)
|
if c, err := d.Client.ContainerInspect(context.Background(), d.Server.Uuid); err != nil {
|
||||||
if err != nil && !client.IsErrNotFound(err) {
|
// 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 againt
|
||||||
|
// a nil-pointer when checking c.State below.
|
||||||
|
//
|
||||||
|
// @see https://github.com/pterodactyl/panel/issues/2000
|
||||||
|
if !client.IsErrNotFound(err) {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
// No reason to try starting a container that is already running.
|
// If the server is running update our internal state and continue on with the attach.
|
||||||
if c.State.Running {
|
if c.State.Running {
|
||||||
d.Server.SetState(ProcessRunningState)
|
d.Server.SetState(ProcessRunningState)
|
||||||
|
|
||||||
return d.Attach()
|
return d.Attach()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Truncate the log file so we don't end up outputting a bunch of useless log information
|
||||||
|
// to the websocket and whatnot. Check first that the path and file exist before trying
|
||||||
|
// to truncate them.
|
||||||
|
if _, err := os.Stat(c.LogPath); err == nil {
|
||||||
|
if err := os.Truncate(c.LogPath, 0); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
d.Server.SetState(ProcessStartingState)
|
d.Server.SetState(ProcessStartingState)
|
||||||
// Set this to true for now, we will set it to false once we reach the
|
// Set this to true for now, we will set it to false once we reach the
|
||||||
// end of this chain.
|
// end of this chain.
|
||||||
@@ -221,15 +235,6 @@ func (d *DockerEnvironment) Start() error {
|
|||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Truncate the log file so we don't end up outputting a bunch of useless log information
|
|
||||||
// to the websocket and whatnot. Check first that the path and file exist before trying
|
|
||||||
// to truncate them.
|
|
||||||
if _, err := os.Stat(c.LogPath); err == nil {
|
|
||||||
if err := os.Truncate(c.LogPath, 0); err != nil {
|
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the configuration files defined for the server before beginning the boot process.
|
// Update the configuration files defined for the server before beginning the boot process.
|
||||||
// This process executes a bunch of parallel updates, so we just block until that process
|
// This process executes a bunch of parallel updates, so we just block until that process
|
||||||
// is completed. Any errors as a result of this will just be bubbled out in the logger,
|
// is completed. Any errors as a result of this will just be bubbled out in the logger,
|
||||||
@@ -350,6 +355,19 @@ func (d *DockerEnvironment) Destroy() error {
|
|||||||
func (d *DockerEnvironment) ExitState() (uint32, bool, error) {
|
func (d *DockerEnvironment) ExitState() (uint32, bool, error) {
|
||||||
c, err := d.Client.ContainerInspect(context.Background(), d.Server.Uuid)
|
c, err := d.Client.ContainerInspect(context.Background(), d.Server.Uuid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// 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
|
||||||
|
// created a new container for it.
|
||||||
|
//
|
||||||
|
// However, someone reported an error in Discord about this scenario happening,
|
||||||
|
// so I guess this should prevent it? They didn't tell me how they caused it though
|
||||||
|
// so thats a mystery that will have to go unsolved.
|
||||||
|
//
|
||||||
|
// @see https://github.com/pterodactyl/panel/issues/2003
|
||||||
|
if client.IsErrNotFound(err) {
|
||||||
|
return 1, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
return 0, false, errors.WithStack(err)
|
return 0, false, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -483,7 +501,7 @@ func (d *DockerEnvironment) EnableResourcePolling() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
s.Resources.CpuAbsolute = s.Resources.CalculateAbsoluteCpu(&v.PreCPUStats, &v.CPUStats)
|
s.Resources.CpuAbsolute = s.Resources.CalculateAbsoluteCpu(&v.PreCPUStats, &v.CPUStats)
|
||||||
s.Resources.Memory = v.MemoryStats.Usage
|
s.Resources.Memory = s.Resources.CalculateDockerMemory(v.MemoryStats)
|
||||||
s.Resources.MemoryLimit = v.MemoryStats.Limit
|
s.Resources.MemoryLimit = v.MemoryStats.Limit
|
||||||
|
|
||||||
// Why you ask? This already has the logic for caching disk space in use and then
|
// Why you ask? This already has the logic for caching disk space in use and then
|
||||||
@@ -547,8 +565,6 @@ func (d *DockerEnvironment) ensureImageExists(c *client.Client) error {
|
|||||||
|
|
||||||
// Creates a new container for the server using all of the data that is currently
|
// Creates a new container for the server using all of the data that is currently
|
||||||
// available for it. If the container already exists it will be returned.
|
// available for it. If the container already exists it will be returned.
|
||||||
//
|
|
||||||
// @todo pull the image being requested if it doesn't exist currently.
|
|
||||||
func (d *DockerEnvironment) Create() error {
|
func (d *DockerEnvironment) Create() error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
cli, err := client.NewClientWithOpts(client.FromEnv)
|
cli, err := client.NewClientWithOpts(client.FromEnv)
|
||||||
@@ -642,17 +658,6 @@ func (d *DockerEnvironment) Create() error {
|
|||||||
NetworkMode: "pterodactyl_nw",
|
NetworkMode: "pterodactyl_nw",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pretty sure TZ=X in the environment variables negates the need for this
|
|
||||||
// to happen. Leaving it until I can confirm that works for everything.
|
|
||||||
//
|
|
||||||
// if err := mountTimezoneData(hostConf); err != nil {
|
|
||||||
// if os.IsNotExist(err) {
|
|
||||||
// zap.S().Warnw("the timezone data path configured does not exist on the system", zap.Error(errors.WithStack(err)))
|
|
||||||
// } else {
|
|
||||||
// zap.S().Warnw("failed to mount timezone data into container", zap.Error(errors.WithStack(err)))
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
if _, err := cli.ContainerCreate(ctx, conf, hostConf, nil, d.Server.Uuid); err != nil {
|
if _, err := cli.ContainerCreate(ctx, conf, hostConf, nil, d.Server.Uuid); err != nil {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
@@ -824,23 +829,21 @@ func (d *DockerEnvironment) exposedPorts() nat.PortSet {
|
|||||||
|
|
||||||
// Formats the resources available to a server instance in such as way that Docker will
|
// Formats the resources available to a server instance in such as way that Docker will
|
||||||
// generate a matching environment in the container.
|
// generate a matching environment in the container.
|
||||||
|
//
|
||||||
|
// This will set the actual memory limit on the container using the multiplier which is the
|
||||||
|
// hard limit for the container (after which will result in a crash). We then set the
|
||||||
|
// reservation to be the expected memory limit based on simply multiplication.
|
||||||
|
//
|
||||||
|
// The swap value is either -1 to disable it, or set to the value of the hard memory limit
|
||||||
|
// plus the additional swap assigned to the server since Docker expects this value to be
|
||||||
|
// the same or higher than the memory limit.
|
||||||
func (d *DockerEnvironment) getResourcesForServer() container.Resources {
|
func (d *DockerEnvironment) getResourcesForServer() container.Resources {
|
||||||
overhead := 1.05
|
|
||||||
// Set the hard limit for memory usage to be 5% more than the amount of memory assigned to
|
|
||||||
// the server. If the memory limit for the server is < 4G, use 10%, if less than 2G use
|
|
||||||
// 15%. This avoids unexpected crashes from processes like Java which run over the limit.
|
|
||||||
if d.Server.Build.MemoryLimit <= 2048 {
|
|
||||||
overhead = 1.15
|
|
||||||
} else if d.Server.Build.MemoryLimit <= 4096 {
|
|
||||||
overhead = 1.10;
|
|
||||||
}
|
|
||||||
|
|
||||||
return container.Resources{
|
return container.Resources{
|
||||||
Memory: int64(math.Round(float64(d.Server.Build.MemoryLimit) * 1000000.0 * overhead)),
|
Memory: d.Server.Build.BoundedMemoryLimit(),
|
||||||
MemoryReservation: d.Server.Build.MemoryLimit * 1000000,
|
MemoryReservation: d.Server.Build.MemoryLimit * 1_000_000,
|
||||||
MemorySwap: d.Server.Build.ConvertedSwap(),
|
MemorySwap: d.Server.Build.ConvertedSwap(),
|
||||||
CPUQuota: d.Server.Build.ConvertedCpuLimit(),
|
CPUQuota: d.Server.Build.ConvertedCpuLimit(),
|
||||||
CPUPeriod: 100000,
|
CPUPeriod: 100_000,
|
||||||
CPUShares: 1024,
|
CPUShares: 1024,
|
||||||
BlkioWeight: d.Server.Build.IoWeight,
|
BlkioWeight: d.Server.Build.IoWeight,
|
||||||
OomKillDisable: &d.Server.Container.OomDisabled,
|
OomKillDisable: &d.Server.Container.OomDisabled,
|
||||||
|
|||||||
@@ -9,7 +9,10 @@ import (
|
|||||||
// should obviously expect memory and CPU usage to be 0. However, disk will always be returned
|
// should obviously expect memory and CPU usage to be 0. However, disk will always be returned
|
||||||
// since that is not dependent on the server being running to collect that data.
|
// since that is not dependent on the server being running to collect that data.
|
||||||
type ResourceUsage struct {
|
type ResourceUsage struct {
|
||||||
// The total amount of memory, in bytes, that this server instance is consuming.
|
// The total amount of memory, in bytes, that this server instance is consuming. This is
|
||||||
|
// calculated slightly differently than just using the raw Memory field that the stats
|
||||||
|
// return from the container, so please check the code setting this value for how that
|
||||||
|
// is calculated.
|
||||||
Memory uint64 `json:"memory_bytes"`
|
Memory uint64 `json:"memory_bytes"`
|
||||||
// The total amount of memory this container or resource can use. Inside Docker this is
|
// The total amount of memory this container or resource can use. Inside Docker this is
|
||||||
// going to be higher than you'd expect because we're automatically allocating overhead
|
// going to be higher than you'd expect because we're automatically allocating overhead
|
||||||
@@ -28,6 +31,27 @@ type ResourceUsage struct {
|
|||||||
} `json:"network"`
|
} `json:"network"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The "docker stats" CLI call does not return the same value as the types.MemoryStats.Usage
|
||||||
|
// value which can be rather confusing to people trying to compare panel usage to
|
||||||
|
// their stats output.
|
||||||
|
//
|
||||||
|
// This math is straight up lifted from their CLI repository in order to show the same
|
||||||
|
// values to avoid people bothering me about it. It should also reflect a slightly more
|
||||||
|
// correct memory value anyways.
|
||||||
|
//
|
||||||
|
// @see https://github.com/docker/cli/blob/96e1d1d6/cli/command/container/stats_helpers.go#L227-L249
|
||||||
|
func (ru *ResourceUsage) CalculateDockerMemory(stats types.MemoryStats) uint64 {
|
||||||
|
if v, ok := stats.Stats["total_inactive_file"]; ok && v < stats.Usage {
|
||||||
|
return stats.Usage - v
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := stats.Stats["inactive_file"]; v < stats.Usage {
|
||||||
|
return stats.Usage - v
|
||||||
|
}
|
||||||
|
|
||||||
|
return stats.Usage
|
||||||
|
}
|
||||||
|
|
||||||
// Calculates the absolute CPU usage used by the server process on the system, not constrained
|
// Calculates the absolute CPU usage used by the server process on the system, not constrained
|
||||||
// by the defined CPU limits on the container.
|
// by the defined CPU limits on the container.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
"github.com/remeh/sizedwaitgroup"
|
"github.com/remeh/sizedwaitgroup"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -112,6 +113,23 @@ func (b *BuildSettings) ConvertedCpuLimit() int64 {
|
|||||||
return b.CpuLimit * 1000
|
return b.CpuLimit * 1000
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set the hard limit for memory usage to be 5% more than the amount of memory assigned to
|
||||||
|
// the server. If the memory limit for the server is < 4G, use 10%, if less than 2G use
|
||||||
|
// 15%. This avoids unexpected crashes from processes like Java which run over the limit.
|
||||||
|
func (b *BuildSettings) MemoryOverheadMultiplier() float64 {
|
||||||
|
if b.MemoryLimit <= 2048 {
|
||||||
|
return 1.15
|
||||||
|
} else if b.MemoryLimit <= 4096 {
|
||||||
|
return 1.10
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1.05
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BuildSettings) BoundedMemoryLimit() int64 {
|
||||||
|
return int64(math.Round(float64(b.MemoryLimit) * b.MemoryOverheadMultiplier() * 1_000_000))
|
||||||
|
}
|
||||||
|
|
||||||
// Returns the amount of swap available as a total in bytes. This is returned as the amount
|
// Returns the amount of swap available as a total in bytes. This is returned as the amount
|
||||||
// of memory available to the server initially, PLUS the amount of additional swap to include
|
// of memory available to the server initially, PLUS the amount of additional swap to include
|
||||||
// which is the format used by Docker.
|
// which is the format used by Docker.
|
||||||
@@ -120,7 +138,7 @@ func (b *BuildSettings) ConvertedSwap() int64 {
|
|||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
return (b.Swap * 1000000) + (b.MemoryLimit * 1000000)
|
return (b.Swap * 1_000_000) + b.BoundedMemoryLimit()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Defines the allocations available for a given server. When using the Docker environment
|
// Defines the allocations available for a given server. When using the Docker environment
|
||||||
|
|||||||
@@ -2,5 +2,5 @@ package system
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// The current version of this software.
|
// The current version of this software.
|
||||||
Version = "1.0.0-beta.3"
|
Version = "1.0.0-beta.4"
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user