cmd/tip: move tip.golang.org from App Engine to Kubernetes on GKE

Change-Id: I52ca7eaca98de27bd920ae01086b3f7724819738
Reviewed-on: https://go-review.googlesource.com/37754
Reviewed-by: Chris Broadfoot <cbro@golang.org>
This commit is contained in:
Brad Fitzpatrick 2017-03-03 20:43:35 +00:00
parent 767744efe2
commit 03d3934baf
7 changed files with 139 additions and 11 deletions

View File

@ -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

15
cmd/tip/Makefile Normal file
View File

@ -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)

View File

@ -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?

View File

@ -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) {

40
cmd/tip/tip-rc.yaml Normal file
View File

@ -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"

16
cmd/tip/tip-service.yaml Normal file
View File

@ -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

View File

@ -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
}