2017-12-19 22:18:27 +00:00
package services
import (
2018-01-09 22:10:56 +00:00
"context"
2017-12-19 22:18:27 +00:00
"crypto/tls"
"fmt"
"io/ioutil"
"net/http"
2018-02-13 00:47:56 +00:00
"strconv"
"strings"
2017-12-19 22:18:27 +00:00
"time"
2018-05-21 18:11:11 +00:00
"github.com/rancher/rke/docker"
2017-12-19 22:18:27 +00:00
"github.com/rancher/rke/hosts"
2018-01-09 22:10:56 +00:00
"github.com/rancher/rke/log"
2018-03-12 19:04:28 +00:00
"github.com/rancher/rke/pki"
2019-08-19 17:53:15 +00:00
"github.com/rancher/rke/pki/cert"
2017-12-19 22:18:27 +00:00
"github.com/sirupsen/logrus"
)
const (
HealthzAddress = "localhost"
HealthzEndpoint = "/healthz"
HTTPProtoPrefix = "http://"
HTTPSProtoPrefix = "https://"
)
2018-03-12 19:04:28 +00:00
func runHealthcheck ( ctx context . Context , host * hosts . Host , serviceName string , localConnDialerFactory hosts . DialerFactory , url string , certMap map [ string ] pki . CertificatePKI ) error {
2018-01-09 22:10:56 +00:00
log . Infof ( ctx , "[healthcheck] Start Healthcheck on service [%s] on host [%s]" , serviceName , host . Address )
2018-03-12 19:04:28 +00:00
var x509Pair tls . Certificate
2018-02-13 00:47:56 +00:00
port , err := getPortFromURL ( url )
if err != nil {
return err
}
2018-05-01 00:25:52 +00:00
if serviceName == KubeAPIContainerName {
certificate := cert . EncodeCertPEM ( certMap [ pki . KubeAPICertName ] . Certificate )
key := cert . EncodePrivateKeyPEM ( certMap [ pki . KubeAPICertName ] . Key )
x509Pair , err = tls . X509KeyPair ( certificate , key )
if err != nil {
return err
}
}
2018-03-12 19:04:28 +00:00
client , err := getHealthCheckHTTPClient ( host , port , localConnDialerFactory , & x509Pair )
2017-12-19 22:18:27 +00:00
if err != nil {
2018-07-10 19:21:27 +00:00
return fmt . Errorf ( "Failed to initiate new HTTP client for service [%s] for host [%s]: %v" , serviceName , host . Address , err )
2017-12-19 22:18:27 +00:00
}
2018-01-09 21:09:41 +00:00
for retries := 0 ; retries < 10 ; retries ++ {
2018-02-13 00:47:56 +00:00
if err = getHealthz ( client , serviceName , host . Address , url ) ; err != nil {
2017-12-19 22:18:27 +00:00
logrus . Debugf ( "[healthcheck] %v" , err )
time . Sleep ( 5 * time . Second )
continue
}
2018-01-09 22:10:56 +00:00
log . Infof ( ctx , "[healthcheck] service [%s] on host [%s] is healthy" , serviceName , host . Address )
2017-12-19 22:18:27 +00:00
return nil
}
2018-05-21 18:11:11 +00:00
logrus . Debug ( "Checking container logs" )
2018-11-24 10:18:24 +00:00
containerLog , _ , logserr := docker . GetContainerLogsStdoutStderr ( ctx , host . DClient , serviceName , "1" , false )
2018-07-21 07:14:45 +00:00
containerLog = strings . TrimSuffix ( containerLog , "\n" )
2018-05-21 18:11:11 +00:00
if logserr != nil {
2018-07-18 20:44:55 +00:00
return fmt . Errorf ( "Failed to verify healthcheck for service [%s]: %v" , serviceName , logserr )
2018-05-21 18:11:11 +00:00
}
return fmt . Errorf ( "Failed to verify healthcheck: %v, log: %v" , err , containerLog )
2017-12-19 22:18:27 +00:00
}
2018-03-12 19:04:28 +00:00
func getHealthCheckHTTPClient ( host * hosts . Host , port int , localConnDialerFactory hosts . DialerFactory , x509KeyPair * tls . Certificate ) ( * http . Client , error ) {
2018-01-11 01:00:14 +00:00
host . LocalConnPort = port
2017-12-19 22:18:27 +00:00
var factory hosts . DialerFactory
2018-01-11 01:00:14 +00:00
if localConnDialerFactory == nil {
factory = hosts . LocalConnFactory
2017-12-19 22:18:27 +00:00
} else {
2018-01-11 01:00:14 +00:00
factory = localConnDialerFactory
2017-12-19 22:18:27 +00:00
}
dialer , err := factory ( host )
if err != nil {
return nil , fmt . Errorf ( "Failed to create a dialer for host [%s]: %v" , host . Address , err )
}
2018-03-12 19:04:28 +00:00
tlsConfig := & tls . Config { InsecureSkipVerify : true }
if x509KeyPair != nil {
tlsConfig = & tls . Config {
InsecureSkipVerify : true ,
Certificates : [ ] tls . Certificate { * x509KeyPair } ,
}
}
2017-12-19 22:18:27 +00:00
return & http . Client {
Transport : & http . Transport {
Dial : dialer ,
2018-03-12 19:04:28 +00:00
TLSClientConfig : tlsConfig ,
2017-12-19 22:18:27 +00:00
} ,
} , nil
}
2018-02-13 00:47:56 +00:00
func getHealthz ( client * http . Client , serviceName , hostAddress , url string ) error {
resp , err := client . Get ( url )
2017-12-19 22:18:27 +00:00
if err != nil {
2018-02-13 00:47:56 +00:00
return fmt . Errorf ( "Failed to check %s for service [%s] on host [%s]: %v" , url , serviceName , hostAddress , err )
2017-12-19 22:18:27 +00:00
}
if resp . StatusCode != http . StatusOK {
statusBody , _ := ioutil . ReadAll ( resp . Body )
2018-03-30 15:38:39 +00:00
return fmt . Errorf ( "Service [%s] is not healthy on host [%s]. Response code: [%d], response body: %s" , serviceName , hostAddress , resp . StatusCode , statusBody )
2017-12-19 22:18:27 +00:00
}
return nil
}
2018-02-13 00:47:56 +00:00
func getPortFromURL ( url string ) ( int , error ) {
port := strings . Split ( strings . Split ( url , ":" ) [ 2 ] , "/" ) [ 0 ]
intPort , err := strconv . Atoi ( port )
if err != nil {
return 0 , err
}
return intPort , nil
}