mirror of
https://github.com/vmorganp/Lazytainer.git
synced 2025-12-21 13:23:02 +01:00
add multi-port support
This commit is contained in:
35
README.md
35
README.md
@@ -18,11 +18,32 @@ $ cd Lazytainer
|
|||||||
$ docker-compose up
|
$ docker-compose up
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Or put in your docker compose
|
||||||
|
```
|
||||||
|
lazytainer:
|
||||||
|
container_name: lazytainer
|
||||||
|
build: .
|
||||||
|
environment:
|
||||||
|
- PORT=81,82 # comma separated list of ports...or just the one
|
||||||
|
- LABEL=lazytainer # value of com.lazytainer.marker for other containers that lazytainer checks
|
||||||
|
# - TIMEOUT=30 # OPTIONAL number of seconds to let container idle
|
||||||
|
# - RXHISTLENGTH=10 # OPTIONAL number of seconds to keep rx history, uptime is calculated as first item and last item from this and must have a gap of at least $MINPACKETTHRESH
|
||||||
|
# - MINPACKETTHRESH=10 # OPTIONAL number of packets that must be recieved to keepalive/start container
|
||||||
|
ports:
|
||||||
|
- 81:81
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
|
||||||
|
whoami1:
|
||||||
|
container_name: whoami1
|
||||||
|
image: containous/whoami
|
||||||
|
command: --port 81 # make this run on the port passed through on lazytainer
|
||||||
|
network_mode: service:lazytainer
|
||||||
|
depends_on:
|
||||||
|
- lazytainer # wait for lazytainer to start before starting
|
||||||
|
labels:
|
||||||
|
- "com.lazytainer.marker=lazytainer" # required label to make it work
|
||||||
|
```
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
- support multiple ports
|
- improve logging - verbosity flags
|
||||||
- test on common services
|
|
||||||
- docker security probably? this really shouldn't be getting exposed except to forward traffic so idk probably firewall all non listed ports?
|
|
||||||
- improve logging
|
|
||||||
- inevitable bugfixes
|
|
||||||
- ???
|
|
||||||
- profit
|
|
||||||
@@ -4,22 +4,33 @@ services:
|
|||||||
container_name: lazytainer
|
container_name: lazytainer
|
||||||
build: .
|
build: .
|
||||||
environment:
|
environment:
|
||||||
- PORT=81 # TODO make this work with more than one port
|
- PORT=81,82 # comma separated list of ports...or just the one
|
||||||
- LABEL=lazytainer # value of com.lazytainer.marker for other containers that lazytainer checks
|
- LABEL=lazytainer # value of com.lazytainer.marker for other containers that lazytainer checks
|
||||||
- TIMEOUT=30 # number of seconds to let container idle
|
# - TIMEOUT=30 # OPTIONAL number of seconds to let container idle
|
||||||
|
# - RXHISTLENGTH=10 # OPTIONAL number of seconds to keep rx history, uptime is calculated as first item and last item from this and must have a gap of at least $MINPACKETTHRESH
|
||||||
|
# - MINPACKETTHRESH=10 # OPTIONAL number of packets that must be recieved to keepalive/start container
|
||||||
ports:
|
ports:
|
||||||
- 81:81
|
- 81:81
|
||||||
|
- 82:82
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
|
||||||
whoami2:
|
whoami1:
|
||||||
container_name: whoami2
|
container_name: whoami1
|
||||||
image: containous/whoami
|
image: containous/whoami
|
||||||
command: --port 81
|
command: --port 81
|
||||||
network_mode: service:lazytainer
|
network_mode: service:lazytainer
|
||||||
depends_on:
|
depends_on:
|
||||||
- lazytainer
|
- lazytainer
|
||||||
# ports:
|
labels:
|
||||||
# - 80:80
|
- "com.lazytainer.marker=lazytainer"
|
||||||
|
|
||||||
|
whoami2:
|
||||||
|
container_name: whoami2
|
||||||
|
image: containous/whoami
|
||||||
|
command: --port 82
|
||||||
|
network_mode: service:lazytainer
|
||||||
|
depends_on:
|
||||||
|
- lazytainer
|
||||||
labels:
|
labels:
|
||||||
- "com.lazytainer.marker=lazytainer"
|
- "com.lazytainer.marker=lazytainer"
|
||||||
275
lazytainer.go
275
lazytainer.go
@@ -1,172 +1,193 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
inactive_seconds := 0
|
inactive_seconds := 0
|
||||||
// check if all of the necessary env vars are set, otherwise, use defaults
|
// check if all of the necessary env vars are set, otherwise, use defaults
|
||||||
// how long a container is allowed to have no traffic before being stopped
|
// how long a container is allowed to have no traffic before being stopped
|
||||||
inactive_timeout, err := strconv.Atoi(os.Getenv("TIMEOUT"))
|
inactive_timeout, err := strconv.Atoi(os.Getenv("TIMEOUT"))
|
||||||
if check_env_fetch_recoverable(err) {
|
if check_env_fetch_recoverable(err) {
|
||||||
fmt.Println("using default because env variable TIMEOUT not set ")
|
fmt.Println("using default because env variable TIMEOUT not set ")
|
||||||
inactive_timeout = 30
|
inactive_timeout = 30
|
||||||
}
|
}
|
||||||
|
|
||||||
// how many polls to keep around
|
// how many polls to keep around
|
||||||
rx_history_length, err := strconv.Atoi(os.Getenv("RXHISTLENGTH"))
|
rx_history_length, err := strconv.Atoi(os.Getenv("RXHISTLENGTH"))
|
||||||
if check_env_fetch_recoverable(err) {
|
if check_env_fetch_recoverable(err) {
|
||||||
fmt.Println("using default because env variable RXHISTLENGTH not set ")
|
fmt.Println("using default because env variable RXHISTLENGTH not set ")
|
||||||
rx_history_length = 10
|
rx_history_length = 10
|
||||||
}
|
}
|
||||||
|
|
||||||
// number of packets required between first and last poll to keep container alive
|
// number of packets required between first and last poll to keep container alive
|
||||||
min_packet_threshhold, err := strconv.Atoi(os.Getenv("MINPACKETTHRESH"))
|
min_packet_threshhold, err := strconv.Atoi(os.Getenv("MINPACKETTHRESH"))
|
||||||
if check_env_fetch_recoverable(err) {
|
if check_env_fetch_recoverable(err) {
|
||||||
fmt.Println("using default because env variable MINPACKETTHRESH not set ")
|
fmt.Println("using default because env variable MINPACKETTHRESH not set ")
|
||||||
min_packet_threshhold = 10
|
min_packet_threshhold = 10
|
||||||
}
|
}
|
||||||
|
|
||||||
// how many seconds to wait in between polls
|
// ports to check for active connections
|
||||||
poll_rate, err := strconv.Atoi(os.Getenv("POLLRATE"))
|
ports := os.Getenv("PORT")
|
||||||
if check_env_fetch_recoverable(err) {
|
check(err)
|
||||||
fmt.Println("using default because env variable POLLRATE not set ")
|
if ports == "" {
|
||||||
poll_rate = 1
|
panic("you must set a port for this to work")
|
||||||
}
|
}
|
||||||
|
ports_arr := strings.Split(string(strings.TrimSpace(string(ports))), ",")
|
||||||
|
portsarg := ""
|
||||||
|
for i, port := range ports_arr {
|
||||||
|
if i == 0 {
|
||||||
|
portsarg += "'"
|
||||||
|
}
|
||||||
|
if i +1 < len(ports_arr){
|
||||||
|
portsarg += string(port)+"\\|"
|
||||||
|
|
||||||
rx_history := make([]int, rx_history_length)
|
}else{
|
||||||
|
portsarg += string(port)+"'"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for {
|
|
||||||
// check if container is on
|
|
||||||
container_state_on := is_container_on()
|
|
||||||
|
|
||||||
// get rx packets outside of the if bc we do it either way
|
// how many seconds to wait in between polls
|
||||||
rx, err := os.ReadFile("/sys/class/net/eth0/statistics/rx_packets")
|
poll_rate, err := strconv.Atoi(os.Getenv("POLLRATE"))
|
||||||
check(err)
|
if check_env_fetch_recoverable(err) {
|
||||||
rx_packets, err := strconv.Atoi(strings.TrimSpace(string(rx)))
|
fmt.Println("using default because env variable POLLRATE not set ")
|
||||||
check(err)
|
poll_rate = 1
|
||||||
rx_history = append(rx_history[1:], rx_packets)
|
}
|
||||||
fmt.Println(rx_packets, "rx packets")
|
|
||||||
|
|
||||||
// if the container is running, see if it needs to be stopped
|
rx_history := make([]int, rx_history_length)
|
||||||
if container_state_on {
|
|
||||||
// get active clients
|
|
||||||
out, err := exec.Command("/bin/sh", "-c", "netstat -n | grep ESTABLISHED | awk '{ print $4 }' | rev | cut -d: -f1| rev | grep -w \"$PORT\" | wc -l").Output() // todo make this handle multiple ports?
|
|
||||||
check(err)
|
|
||||||
active_clients, err := strconv.Atoi(strings.TrimSpace(string(out)))
|
|
||||||
check(err)
|
|
||||||
|
|
||||||
// log out the results
|
for {
|
||||||
fmt.Println(active_clients, "active clients")
|
// check if container is on
|
||||||
// println(rx_packets, "rx packets")
|
container_state_on := is_container_on()
|
||||||
// fmt.Printf("%v rx history", rx_history)
|
|
||||||
|
|
||||||
if active_clients == 0 && rx_history[0]+min_packet_threshhold > rx_history[len(rx_history)-1] {
|
// get rx packets outside of the if bc we do it either way
|
||||||
// count up if we have no active clients
|
rx, err := os.ReadFile("/sys/class/net/eth0/statistics/rx_packets")
|
||||||
// if no clients are active and less than 10 packets recieved in the last 10 seconds
|
check(err)
|
||||||
inactive_seconds++
|
rx_packets, err := strconv.Atoi(strings.TrimSpace(string(rx)))
|
||||||
fmt.Println(inactive_seconds, "seconds without an active client")
|
check(err)
|
||||||
if inactive_seconds > inactive_timeout {
|
rx_history = append(rx_history[1:], rx_packets)
|
||||||
stop_containers()
|
fmt.Println(rx_packets, "rx packets")
|
||||||
}
|
|
||||||
} else {
|
// if the container is running, see if it needs to be stopped
|
||||||
inactive_seconds = 0
|
if container_state_on {
|
||||||
}
|
// get active clients
|
||||||
} else {
|
out, err := exec.Command("/bin/sh", "-c", "netstat -n | grep ESTABLISHED | awk '{ print $4 }' | rev | cut -d: -f1| rev | grep "+portsarg+" | wc -l").Output() // todo make this handle multiple ports?
|
||||||
// if more than 10 rx in last 10 seconds, start the container
|
check(err)
|
||||||
if rx_history[0]+min_packet_threshhold < rx_history[len(rx_history)-1] {
|
active_clients, err := strconv.Atoi(strings.TrimSpace(string(out)))
|
||||||
inactive_seconds = 0
|
check(err)
|
||||||
start_containers()
|
|
||||||
}
|
// log out the results
|
||||||
}
|
fmt.Println(active_clients, "active clients")
|
||||||
fmt.Println("//////////////////////////////////////////////////////////////////////////////////")
|
// println(rx_packets, "rx packets")
|
||||||
for i := 0; i < poll_rate; i++ {
|
// fmt.Printf("%v rx history", rx_history)
|
||||||
time.Sleep(time.Second)
|
|
||||||
}
|
if active_clients == 0 && rx_history[0]+min_packet_threshhold > rx_history[len(rx_history)-1] {
|
||||||
}
|
// count up if we have no active clients
|
||||||
|
// if no clients are active and less than 10 packets recieved in the last 10 seconds
|
||||||
|
inactive_seconds++
|
||||||
|
fmt.Println(inactive_seconds, "seconds without an active client")
|
||||||
|
if inactive_seconds > inactive_timeout {
|
||||||
|
stop_containers()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
inactive_seconds = 0
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// if more than 10 rx in last 10 seconds, start the container
|
||||||
|
if rx_history[0]+min_packet_threshhold < rx_history[len(rx_history)-1] {
|
||||||
|
inactive_seconds = 0
|
||||||
|
start_containers()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Println("//////////////////////////////////////////////////////////////////////////////////")
|
||||||
|
for i := 0; i < poll_rate; i++ {
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type container struct {
|
type container struct {
|
||||||
id string
|
id string
|
||||||
state string
|
state string
|
||||||
}
|
}
|
||||||
|
|
||||||
func newContainer(id string, state string) *container {
|
func newContainer(id string, state string) *container {
|
||||||
c := container{id: id}
|
c := container{id: id}
|
||||||
c.state = state
|
c.state = state
|
||||||
return &c
|
return &c
|
||||||
}
|
}
|
||||||
|
|
||||||
func get_containers() []container {
|
func get_containers() []container {
|
||||||
containers := []container{}
|
containers := []container{}
|
||||||
out, err := exec.Command("/bin/sh", "-c", "docker ps -a --no-trunc --filter label=\"com.lazytainer.marker=$LABEL\" --format \"{{.ID}} {{.State}}\"").Output() // todo make this handle multiple ports?
|
out, err := exec.Command("/bin/sh", "-c", "docker ps -a --no-trunc --filter label=\"com.lazytainer.marker=$LABEL\" --format \"{{.ID}} {{.State}}\"").Output() // todo make this handle multiple ports?
|
||||||
check(err)
|
check(err)
|
||||||
fmt.Println(string(out))
|
fmt.Println(string(out))
|
||||||
if strings.TrimSpace(string(out)) == "" {
|
if strings.TrimSpace(string(out)) == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
lines := strings.Split(string(strings.TrimSpace(string(out))), "\n")
|
lines := strings.Split(string(strings.TrimSpace(string(out))), "\n")
|
||||||
for _, s := range lines {
|
for _, s := range lines {
|
||||||
containers = append(containers, container{strings.Split(s, " ")[0], strings.Split(s, " ")[1]})
|
containers = append(containers, container{strings.Split(s, " ")[0], strings.Split(s, " ")[1]})
|
||||||
}
|
}
|
||||||
return containers
|
return containers
|
||||||
}
|
}
|
||||||
|
|
||||||
func is_container_on() bool {
|
func is_container_on() bool {
|
||||||
containers := get_containers()
|
containers := get_containers()
|
||||||
for _, c := range containers {
|
for _, c := range containers {
|
||||||
if c.state == "running" {
|
if c.state == "running" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func stop_containers() {
|
func stop_containers() {
|
||||||
fmt.Println("stopping container(s)")
|
fmt.Println("stopping container(s)")
|
||||||
containers := get_containers()
|
containers := get_containers()
|
||||||
idString := ""
|
idString := ""
|
||||||
for _, c := range containers {
|
for _, c := range containers {
|
||||||
idString = idString + " " + c.id
|
idString = idString + " " + c.id
|
||||||
}
|
}
|
||||||
|
|
||||||
out, err := exec.Command("/bin/sh", "-c", "docker stop "+idString).Output()
|
out, err := exec.Command("/bin/sh", "-c", "docker stop "+idString).Output()
|
||||||
check(err)
|
check(err)
|
||||||
fmt.Println(string(out))
|
fmt.Println(string(out))
|
||||||
}
|
}
|
||||||
|
|
||||||
func start_containers() {
|
func start_containers() {
|
||||||
fmt.Println("starting container(s)")
|
fmt.Println("starting container(s)")
|
||||||
containers := get_containers()
|
containers := get_containers()
|
||||||
idString := ""
|
idString := ""
|
||||||
for _, c := range containers {
|
for _, c := range containers {
|
||||||
idString = idString + " " + c.id
|
idString = idString + " " + c.id
|
||||||
}
|
}
|
||||||
|
|
||||||
out, err := exec.Command("/bin/sh", "-c", "docker start "+idString).Output()
|
out, err := exec.Command("/bin/sh", "-c", "docker start "+idString).Output()
|
||||||
check(err)
|
check(err)
|
||||||
fmt.Println(string(out))
|
fmt.Println(string(out))
|
||||||
}
|
}
|
||||||
|
|
||||||
func check(err error) {
|
func check(err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
// panic(err)
|
// panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func check_env_fetch_recoverable(err error) bool {
|
func check_env_fetch_recoverable(err error) bool {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "strconv.Atoi: parsing \"\": invalid syntax") {
|
if strings.Contains(err.Error(), "strconv.Atoi: parsing \"\": invalid syntax") {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user