diff --git a/cmd/tip/Dockerfile b/cmd/tip/Dockerfile index d258f7f733..9c80137ffa 100644 --- a/cmd/tip/Dockerfile +++ b/cmd/tip/Dockerfile @@ -1,13 +1,15 @@ -FROM golang:1.6 +FROM golang:1.8 RUN apt-get update && apt-get install --no-install-recommends -y -q build-essential git # golang puts its go install here (weird but true) ENV GOROOT_BOOTSTRAP /usr/local/go +RUN go get -d golang.org/x/crypto/acme/autocert + # golang sets GOPATH=/go ADD . /go/src/tip RUN go install tip ENTRYPOINT ["/go/bin/tip"] -# Kubernetes expects us to listen on port 8080 +# App Engine expects us to listen on port 8080 EXPOSE 8080 diff --git a/cmd/tip/Makefile b/cmd/tip/Makefile new file mode 100644 index 0000000000..5844307b18 --- /dev/null +++ b/cmd/tip/Makefile @@ -0,0 +1,15 @@ +# Copyright 2017 The Go Authors. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +VERSION=v1 + +docker-prod: Dockerfile + docker build -f Dockerfile --tag=gcr.io/symbolic-datum-552/tip:$(VERSION) . +docker-dev: Dockerfile + docker build -f Dockerfile --tag=gcr.io/go-dashboard-dev/tip:$(VERSION) . + +push-prod: docker-prod + gcloud docker push -- gcr.io/symbolic-datum-552/tip:$(VERSION) +push-dev: docker-dev + gcloud docker push -- gcr.io/go-dashboard-dev/tip:$(VERSION) diff --git a/cmd/tip/README b/cmd/tip/README index e131586304..9b7d9665b0 100644 --- a/cmd/tip/README +++ b/cmd/tip/README @@ -1,7 +1,11 @@ +============================================================ +Old instructions, only valid for talks.golang.org: +============================================================ + 1. Deploy the app. To deploy tip.golang.org: - $ gcloud --project golang-org app deploy --no-promote godoc.yaml + (See Kubernetes instruction below.) To deploy talks.golang.org: $ gcloud --project golang-org app deploy --no-promote talks.yaml @@ -12,3 +16,18 @@ https://console.developers.google.com/appengine/versions?project=golang-org&moduleId=tip 4. Clean up any old versions (they continue to use at least one instance). + +============================================================ +New Kubernetes instructions, for tip.golang.org: +============================================================ + +Kubernetes instructions: + + * build & push images (see Makefile for helpers) + * create/update resources: + - kubectl create -f tip-rc.yaml + - kubectl create -f tip-service.yaml + +TODO(bradfitz): flesh out these instructions as I gain experience +with updating this over time. Also: move talks.golang.org to GKE too? + diff --git a/cmd/tip/godoc.go b/cmd/tip/godoc.go index d16698cb06..a01b8ca2cc 100644 --- a/cmd/tip/godoc.go +++ b/cmd/tip/godoc.go @@ -17,7 +17,7 @@ type godocBuilder struct { } func (b godocBuilder) Signature(heads map[string]string) string { - return heads["go"] + "-" + heads["tools"] + return fmt.Sprintf("go=%v/tools=%v", heads["go"], heads["tools"]) } func (b godocBuilder) Init(dir, hostport string, heads map[string]string) (*exec.Cmd, error) { diff --git a/cmd/tip/tip-rc.yaml b/cmd/tip/tip-rc.yaml new file mode 100644 index 0000000000..139e614cbf --- /dev/null +++ b/cmd/tip/tip-rc.yaml @@ -0,0 +1,40 @@ +apiVersion: v1 +kind: ReplicationController +metadata: + name: tipgodoc-v1 +spec: + replicas: 1 + selector: + app: tipgodoc + template: + metadata: + name: tipgodoc + labels: + app: tipgodoc + spec: + volumes: + - name: cache-volume + emptyDir: {} + containers: + - name: gitmirror + image: gcr.io/symbolic-datum-552/tip:v1 + imagePullPolicy: Always + command: ["/go/bin/tip", "--autocert=tip.golang.org"] + env: + - name: TMPDIR + value: /build + - name: TIP_BUILDER + value: godoc + volumeMounts: + - mountPath: /build + name: cache-volume + ports: + - containerPort: 8080 + - containerPort: 443 + resources: + requests: + cpu: "1" + memory: "2Gi" + limits: + cpu: "2" + memory: "4Gi" diff --git a/cmd/tip/tip-service.yaml b/cmd/tip/tip-service.yaml new file mode 100644 index 0000000000..eeb2a1cb53 --- /dev/null +++ b/cmd/tip/tip-service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: tipgodoc +spec: + ports: + - port: 80 + targetPort: 8080 + name: http + - port: 443 + targetPort: 443 + name: https + selector: + app: tipgodoc + type: LoadBalancer + loadBalancerIP: 130.211.180.236 diff --git a/cmd/tip/tip.go b/cmd/tip/tip.go index f8171bd6e5..81d1054d23 100644 --- a/cmd/tip/tip.go +++ b/cmd/tip/tip.go @@ -8,8 +8,10 @@ package main import ( "bufio" + "crypto/tls" "encoding/json" "errors" + "flag" "fmt" "io" "io/ioutil" @@ -22,6 +24,8 @@ import ( "path/filepath" "sync" "time" + + "golang.org/x/crypto/acme/autocert" ) const ( @@ -30,7 +34,15 @@ const ( startTimeout = 10 * time.Minute ) +var startTime = time.Now() + +var ( + autoCertDomain = flag.String("autocert", "", "if non-empty, listen on port 443 and serve a LetsEncrypt cert for this hostname") +) + func main() { + flag.Parse() + const k = "TIP_BUILDER" var b Builder switch os.Getenv(k) { @@ -47,9 +59,28 @@ func main() { http.Handle("/", httpsOnlyHandler{p}) http.HandleFunc("/_ah/health", p.serveHealthCheck) - log.Print("Starting up") + log.Printf("Starting up tip server for builder %q", os.Getenv(k)) - if err := http.ListenAndServe(":8080", nil); err != nil { + errc := make(chan error) + + go func() { + errc <- http.ListenAndServe(":8080", nil) + }() + if *autoCertDomain != "" { + log.Printf("Listening on port 443 with LetsEncrypt support on domain %q", *autoCertDomain) + m := autocert.Manager{ + Prompt: autocert.AcceptTOS, + HostPolicy: autocert.HostWhitelist(*autoCertDomain), + } + s := &http.Server{ + Addr: ":https", + TLSConfig: &tls.Config{GetCertificate: m.GetCertificate}, + } + go func() { + errc <- s.ListenAndServeTLS("", "") + }() + } + if err := <-errc; err != nil { p.stop() log.Fatal(err) } @@ -98,14 +129,17 @@ func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (p *Proxy) serveStatus(w http.ResponseWriter, r *http.Request) { p.mu.Lock() defer p.mu.Unlock() - fmt.Fprintf(w, "side=%v\ncurrent=%v\nerror=%v\n", p.side, p.cur, p.err) + fmt.Fprintf(w, "side=%v\ncurrent=%v\nerror=%v\nuptime=%v\n", p.side, p.cur, p.err, int(time.Since(startTime).Seconds())) } func (p *Proxy) serveHealthCheck(w http.ResponseWriter, r *http.Request) { p.mu.Lock() defer p.mu.Unlock() - // NOTE: Status 502, 503, 504 are the only status codes that signify an unhealthy app. - // So long as this handler returns one of those codes, this instance will not be sent any requests. + + // NOTE: (App Engine only; not GKE) Status 502, 503, 504 are + // the only status codes that signify an unhealthy app. So + // long as this handler returns one of those codes, this + // instance will not be sent any requests. if p.proxy == nil { log.Printf("Health check: not ready") http.Error(w, "Not ready", http.StatusServiceUnavailable) @@ -266,11 +300,13 @@ func checkout(repo, hash, path string) error { return nil } +var timeoutClient = &http.Client{Timeout: 10 * time.Second} + // gerritMetaMap returns the map from repo name (e.g. "go") to its // latest master hash. // The returned map is nil on any transient error. func gerritMetaMap() map[string]string { - res, err := http.Get(metaURL) + res, err := timeoutClient.Get(metaURL) if err != nil { return nil } @@ -309,7 +345,7 @@ func gerritMetaMap() map[string]string { } func getOK(url string) (body []byte, err error) { - res, err := http.Get(url) + res, err := timeoutClient.Get(url) if err != nil { return nil, err }