Merge pull request #33 from theaog/kc-kill-container-tool

feat: add ContainerGuard tool
This commit is contained in:
SkyperTHC 2022-10-31 17:10:15 +00:00 committed by GitHub
commit 5a6118345a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 466 additions and 2 deletions

2
cleaner/cg/.gitignore vendored Normal file

@ -0,0 +1,2 @@
cg
todo.md

9
cleaner/cg/Dockerfile Normal file

@ -0,0 +1,9 @@
FROM golang:1.19 as BUILD
WORKDIR /app
COPY *.go go.mod go.sum /app/
RUN go mod tidy && go mod vendor
RUN CGO_ENABLED=0 go build -ldflags='-s -w' -o cg
FROM scratch
COPY --from=BUILD /app/cg /app/cg
ENTRYPOINT ["/app/cg"]

21
cleaner/cg/Makefile Normal file

@ -0,0 +1,21 @@
build: pre
GOOS=linux GOARCH=amd64 go build
upload:
scp -P64222 cg root@bh.segfault.net:/usr/bin/cg.new
ssh -p64222 root@bh.segfault.net mv /usr/bin/cg.new /usr/bin/cg
build-arm: pre
GOOS=linux GOARCH=arm go build -o cg-arm
pre:
go mod tidy
docker:
docker built -t cg .
docker run -it --rm cg
release: build
sha256sum cg | tee cg.sum
tar czvf cg.tgz cg cg.sum
rm -f cg cg.sum

BIN
cleaner/cg/cg.tgz Normal file

Binary file not shown.

29
cleaner/cg/go.mod Normal file

@ -0,0 +1,29 @@
module cg
go 1.19
require (
github.com/docker/docker v20.10.20+incompatible
github.com/sirupsen/logrus v1.9.0
golang.org/x/sys v0.1.0
)
require (
github.com/Microsoft/go-winio v0.6.0 // indirect
github.com/docker/distribution v2.8.1+incompatible // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/go-cmp v0.5.8 // indirect
github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/stretchr/testify v1.8.0 // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b // indirect
golang.org/x/time v0.1.0 // indirect
golang.org/x/tools v0.1.12 // indirect
gotest.tools/v3 v3.4.0 // indirect
)

97
cleaner/cg/go.sum Normal file

@ -0,0 +1,97 @@
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg=
github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE=
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68=
github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v20.10.20+incompatible h1:kH9tx6XO+359d+iAkumyKDc5Q1kOwPuAUaeri48nD6E=
github.com/docker/docker v20.10.20+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae h1:O4SWKdcHVCvYqyDV+9CJA1fcDN2L11Bule0iFy3YlAI=
github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM=
github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b h1:ZmngSVLe/wycRns9MKikG9OWIEjGcGAkacif7oYQaUY=
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA=
golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g=

271
cleaner/cg/main.go Normal file

@ -0,0 +1,271 @@
package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"sync"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/client"
"github.com/sirupsen/logrus"
log "github.com/sirupsen/logrus"
)
func init() {
if *debugFlag {
log.SetLevel(log.DebugLevel)
log.SetReportCaller(true)
}
log.SetFormatter(&logrus.TextFormatter{
ForceColors: true,
})
}
// CLI flags
var (
strainFlag = flag.Float64("strain", 20, "maximum amount of strain per CPU core")
pathFlag = flag.String("path", "/sf/config/db/cg", "directory path where action logs are stored")
debugFlag = flag.Bool("debug", false, "activate debug mode")
)
func main() {
flag.Parse()
log.Infof("ContainerGuard (CG) started protecting your Segfault.Net instance...")
// docker client
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
panic(err)
}
log.Debugf("connected to docker client v%v", cli.ClientVersion())
// number of virtual cores
var numCPU = runtime.NumCPU()
// MAX_LOAD defines the maximum amount of `strain` each CPU can have
// before triggering our cleanup tasks.
var MAX_LOAD = *strainFlag * float64(numCPU)
// last recorded loadavg after a trigger event
var LAST_LOAD float64 // default value 0.0
var loop_time_s = 5
var count int
for range time.Tick(time.Second * time.Duration(loop_time_s)) {
// protect legitimate users
if LAST_LOAD != 0.0 { // we got a trigger event
// after 60s stop protecting
if count > 60/loop_time_s {
LAST_LOAD = 0.0
count = 0
continue
}
if sysLoad1mAvg() <= LAST_LOAD {
LAST_LOAD = sysLoad1mAvg()
count++
continue
}
// if load doesn't go down every 5s
LAST_LOAD = 0.0 // reset
}
if sysLoad1mAvg() <= MAX_LOAD {
continue
}
log.Warnf("[TRIGGER] load (%.2f) on cpu (%v) higher than max_load (%v)", sysLoad1mAvg(), numCPU, MAX_LOAD)
LAST_LOAD = sysLoad1mAvg()
err = stopContainersBasedOnUsage(cli)
if err != nil {
log.Error(err)
}
}
}
// stopContainersBasedOnUsage iterates through all the containers on the system
// to find abusive ones and stops them, but only if their name starts w/ lg-*
func stopContainersBasedOnUsage(cli *client.Client) error {
const filterPrefix = "/lg-*"
opts := types.ContainerListOptions{}
opts.All = false // list only running containers
opts.Filters = filters.NewArgs()
opts.Filters.Add("name", filterPrefix)
ctx := context.Background()
list, err := cli.ContainerList(ctx, opts)
if err != nil {
return err
}
// mu protects `_largestUsage`
var mu sync.Mutex
var highestUsage float64
// used to synchronize goroutines
var wg = &sync.WaitGroup{}
// check all containers usage and keep largest value in `largestUsage` var
for _, c := range list {
wg.Add(1)
go func(c types.Container) {
defer wg.Done()
usage := containerUsage(cli, c.ID)
if usage > highestUsage {
mu.Lock()
highestUsage = usage
mu.Unlock()
}
log.Infof("[%v] usage (%.2f%%)", c.Names[0][1:], usage)
}(c)
}
wg.Wait()
log.Infof("[HIGHEST USAGE] %.2f%%", highestUsage)
for _, c := range list {
wg.Add(1)
go func(c types.Container) {
defer wg.Done()
usage := containerUsage(cli, c.ID)
log.Debugf("allowed to kill %v with usage %v", c.Names[0], usage)
var killTimeout = time.Second * 2
var killThreshold = highestUsage * 0.8
const action = "STOP in 2s or KILL"
// stop all containers where usage > `highestUsage` * 0.8
if usage > killThreshold {
log.Warnf("[%v] usage (%.2f%%) > threshold (%.2f%%) | action %v", c.Names[0][1:], usage, killThreshold, action)
ctx := context.Background()
err := cli.ContainerStop(ctx, c.ID, &killTimeout)
if err != nil {
log.Error(err)
return
}
}
// log stopped containers to disk
logData := LogData{
name: c.Names[0],
usage: usage,
threshold: killThreshold,
load: sysLoad1mAvg(),
action: action,
}
if err := logData.save(*pathFlag); err != nil {
log.Error(err)
return
}
}(c)
}
wg.Wait()
return nil
}
// containerUsage calculates the CPU usage of a container.
func containerUsage(cli *client.Client, cID string) float64 {
ctx := context.Background()
stats, err := cli.ContainerStats(ctx, cID, false)
if err != nil {
log.Error(err)
return 0
}
defer stats.Body.Close()
var result ContainerStatsData
err = json.NewDecoder(stats.Body).Decode(&result)
if err != nil {
log.Error(err)
b, _ := ioutil.ReadAll(stats.Body)
log.Error(b)
}
// https://github.com/docker/cli/blob/53f8ed4bec07084db4208f55987a2ea94b7f01d6/cli/command/container/stats_helpers.go#L166
// calculations
cpu_delta := float64(result.CPUStats.CPUUsage.TotalUsage) - float64(result.PrecpuStats.CPUUsage.TotalUsage)
system_cpu_delta := result.CPUStats.SystemCPUUsage - result.PrecpuStats.SystemCPUUsage
number_cpus := result.CPUStats.OnlineCpus
usage := (float64(cpu_delta) / float64(system_cpu_delta)) * float64(number_cpus) * 100.0
return usage
}
type LogData struct {
name string
usage float64
threshold float64
load float64
action string
}
// run mkdir only once
var mkdirOnce = sync.Once{}
func (a LogData) save(path string) error {
var err error
mkdirOnce.Do(func() {
err = os.MkdirAll(path, 0770)
})
if err != nil {
return err
}
t := time.Now().UTC().Unix()
// example: 1666389757 usage=95.71 threshold=28.61 load=200.23 action=SIGKILL
data := fmt.Sprintf("%v usage=%.2f threshold=%.2f load=%.2f action=%s ", t, a.usage, a.threshold, a.load, a.action)
filePath := filepath.Join(path, a.name+".txt")
if err := os.WriteFile(filePath, []byte(data), 0660); err != nil {
return err
}
log.Debugf("[LOG FILE] %v", filePath)
return nil
}
type ContainerStatsData struct {
CPUStats struct {
CPUUsage struct {
UsageInUsermode int `json:"usage_in_usermode"`
TotalUsage int `json:"total_usage"`
UsageInKernelmode int `json:"usage_in_kernelmode"`
} `json:"cpu_usage"`
SystemCPUUsage int `json:"system_cpu_usage"`
OnlineCpus int `json:"online_cpus"`
ThrottlingData struct {
Periods int `json:"periods"`
ThrottledPeriods int `json:"throttled_periods"`
ThrottledTime int `json:"throttled_time"`
} `json:"throttling_data"`
} `json:"cpu_stats"`
PrecpuStats struct {
CPUUsage struct {
PercpuUsage []int `json:"percpu_usage"`
UsageInUsermode int `json:"usage_in_usermode"`
TotalUsage int `json:"total_usage"`
UsageInKernelmode int `json:"usage_in_kernelmode"`
} `json:"cpu_usage"`
SystemCPUUsage int `json:"system_cpu_usage"`
OnlineCpus int `json:"online_cpus"`
ThrottlingData struct {
Periods int `json:"periods"`
ThrottledPeriods int `json:"throttled_periods"`
ThrottledTime int `json:"throttled_time"`
} `json:"throttling_data"`
} `json:"precpu_stats"`
}

10
cleaner/cg/readme.md Normal file

@ -0,0 +1,10 @@
# CG stands for ContainerGuard
### What is this for?
it's a container guard(monitoring) service aimed at identifying and stopping abuse
### What does it do?
atm, it only monitors container CPU usage and kills misbehaving containers
### What will it do in the future?
all sort of guarding(monitoring) and punishment behaviors

@ -0,0 +1,17 @@
//go:build linux
// build +linux
package main
import "golang.org/x/sys/unix"
func sysLoad1mAvg() float64 {
var info unix.Sysinfo_t
unix.Sysinfo(&info)
const si_load_shift = 16
load := float64(info.Loads[0]) / float64(1<<si_load_shift)
return load
}

@ -66,6 +66,15 @@ services:
- "/var/run/docker.sock:/var/run/docker.sock"
- "${SF_BASEDIR:-.}/sfbin:/sf/bin:ro"
sf-containerguard:
build: cleaner/cg
image: sf-containerguard
container_name: sf-containerguard
restart: ${SF_RESTART:-on-failure}
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
- ${SF_BASEDIR:-.}/config:${SF_BASEDIR:-.}/config
sf-portd:
build: encfsd
image: sf-encfsd
@ -262,7 +271,7 @@ services:
vpn-net:
ipv4_address: 172.20.0.111
cap_add:
- NET_ADMIN
- NET_ADMIN
restart: ${SF_RESTART:-on-failure}
dns: 172.20.0.53
depends_on:
@ -407,4 +416,3 @@ networks:
config:
- subnet: 10.11.0.0/16
# default gw is always 10.11.0.1 and is the host side of the bridge (?)