minio/dockerscripts/healthcheck.go

182 lines
5 KiB
Go
Executable file

// +build ignore
/*
* MinIO Cloud Storage, (C) 2019 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package main
import (
"bufio"
"crypto/tls"
"errors"
"fmt"
"log"
"net/http"
"net/url"
"os"
"os/exec"
"strings"
"time"
xhttp "github.com/minio/minio/cmd/http"
)
const (
initGraceTime = 300
healthPath = "/minio/health/live"
timeout = time.Duration(30 * time.Second)
tcp = "tcp"
anyIPv6 = ":::"
anyIPv4 = "0.0.0.0"
)
// returns container boot time by finding
// modtime of /proc/1 directory
func getStartTime() time.Time {
di, err := os.Stat("/proc/1")
if err != nil {
// Cant stat proc dir successfully, exit with error
log.Fatalln(err)
}
return di.ModTime()
}
// Returns the ip:port of the MinIO process
// running in the container
func findEndpoint() (string, error) {
cmd := exec.Command("netstat", "-ntlp")
stdout, err := cmd.StdoutPipe()
if err != nil {
// error getting stdout pipe
return "", err
}
if err = cmd.Start(); err != nil {
// error executing the command.
return "", err
}
// split netstat output in rows
scanner := bufio.NewScanner(stdout)
scanner.Split(bufio.ScanLines)
// MinIO works on TCP and it is supposed to be
// the only process listening on a port on any IP address
// (on ::: or 0.0.0.0) inside container.
// Since MinIO may run as non-root user, we can
// not depend on the PID/Program name column
// of netstat output
for scanner.Scan() {
line := scanner.Text()
if strings.Contains(line, tcp) && (strings.Contains(line, anyIPv6) || strings.Contains(line, anyIPv4)) {
newLine := strings.Replace(line, anyIPv4, "127.0.0.1:", 1)
if strings.Contains(line, anyIPv6) {
newLine = strings.Replace(line, anyIPv6, "::1:", 1)
}
fields := strings.Fields(newLine)
// index 3 in the row has the Local address
// find the last index of ":" - address will
// have port number after this index
i := strings.LastIndex(fields[3], ":")
// split address and port
addr := fields[3][:i]
port := fields[3][i+1:]
// add surrounding [] for ip6 address
if strings.Count(addr, ":") > 0 {
addr = strings.Join([]string{"[", addr, "]"}, "")
}
// wait for cmd to complete before return
if err = cmd.Wait(); err != nil {
return "", err
}
// return joint address and port
return strings.Join([]string{addr, port}, ":"), nil
}
}
if err = scanner.Err(); err != nil {
return "", err
}
if err = cmd.Wait(); err != nil {
// command failed to run
return "", err
}
// minio process not found, exit with error
return "", errors.New("no minio process found")
}
func main() {
startTime := getStartTime()
// In distributed environment like Swarm, traffic is routed
// to a container only when it reports a `healthy` status. So, we exit
// with 0 to ensure healthy status till distributed MinIO starts (120s).
// Refer: https://github.com/moby/moby/pull/28938#issuecomment-301753272
if (time.Now().Sub(startTime) / time.Second) > initGraceTime {
endPoint, err := findEndpoint()
if err != nil {
log.Fatalln(err)
}
u, err := url.Parse(fmt.Sprintf("http://%s%s", endPoint, healthPath))
if err != nil {
// Could not parse URL successfully
log.Fatalln(err)
}
// MinIO server may be using self-signed or CA certificates. To avoid
// making Docker setup complicated, we skip verifying certificates here.
// This is because, following request tests for health status within
// containerized environment, i.e. requests are always made to the MinIO
// server running on the same host.
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Transport: tr, Timeout: timeout}
resp, err := client.Get(u.String())
if err != nil {
// GET failed exit
log.Fatalln(err)
}
if resp.StatusCode == http.StatusOK {
// Drain any response.
xhttp.DrainBody(resp.Body)
// exit with success
os.Exit(0)
}
// Drain any response.
xhttp.DrainBody(resp.Body)
// 400 response may mean sever is configured with https
if resp.StatusCode == http.StatusBadRequest {
// Try with https
u.Scheme = "https"
resp, err = client.Get(u.String())
if err != nil {
// GET failed exit
log.Fatalln(err)
}
if resp.StatusCode == http.StatusOK {
// Drain any response.
xhttp.DrainBody(resp.Body)
// exit with success
os.Exit(0)
}
// Drain any response.
xhttp.DrainBody(resp.Body)
}
// Execution reaching here means none of
// the success cases were satisfied
os.Exit(1)
}
os.Exit(0)
}