mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 17:31:43 +00:00
This PR ships our freshly rewritten container images built with rules_oci and Wolfi, which for now will only be used on S2. *What is this about* This work is the conjunction of [hardening container images](https://github.com/orgs/sourcegraph/projects/302?pane=issue&itemId=25019223) and fully building our container images with Bazel. * All base images are now distroless, based on Wolfi, meaning we fully control every little package version and we won't be subject anymore to Alpine maintainers dropping a postgres version for example. * Container images are now built with `rules_oci`, meaning we don't have Dockerfile anymore, but instead created through [Bazel rules](https://sourcegraph.sourcegraph.com/github.com/sourcegraph/sourcegraph@bzl/oci_wolfi/-/blob/enterprise/cmd/gitserver/BUILD.bazel). Don't be scared, while this will look a bit strange to you at first, it's much saner and simpler to do than our Dockerfiles and their muddy shell scripts calling themselves in cascade. :spiral_note_pad: *Plan*: *1/ (NOW) We merge our branch on `main` today, here is what it does change for you 👇:skin-tone-3::* * On `main`: * It will introduce a new job on `main` _Bazel Push_, which will push those new images on our registries with all tags prefixed by `bazel-`. * These new images will be picked up by S2 and S2 only. * The existing jobs building docker images and pushing them will stay in place until we have QA'ed them enough and are confident to roll them out on Dotcom. * Because we'll be building both images, there will be more jobs running on `main`, but this should not affect the wall clock time. * On all branches (so your PRs and `main`) * The _Bazel Test_ job will now run: Backend Integration Tests, E2E Tests and CodeIntel QA * This will increase the duration of your test jobs in PRs, but as we haven't removed yet the `sg lint` step, it should not affect too much the wall clock time of your PRs. * But it will also increase your confidence toward your changes, as the coverage will vastly increased compared to before. * If you have ongoing branches which are affecting the docker images (like adding a new binary, like the recent `scip-tags`, reach us out on #job-fair-bazel so we can help you to port your changes. It's much much simpler than before, but it's going to be unfamiliar to you). * If something goes awfully wrong, we'll rollback and update this thread. *2/ (EOW / Early next week) Once we're confident enough with what we saw on S2, we'll roll the new images on Dotcom.* * After the first successful deploy and a few sanity checks, we will drop the old images building jobs. * At this point, we'll reach out to all TLs asking for their help to exercise all features of our product to ensure we catch any potential breakage. ## Test plan <!-- All pull requests REQUIRE a test plan: https://docs.sourcegraph.com/dev/background-information/testing_principles --> * We tested our new images on `scale-testing` and it worked. * The new container building rules comes with _container tests_ which ensures that produced images are containing and configured with what should be in there: [example](https://sourcegraph.sourcegraph.com/github.com/sourcegraph/sourcegraph@bzl/oci_wolfi/-/blob/enterprise/cmd/gitserver/image_test.yaml) . --------- Co-authored-by: Dave Try <davetry@gmail.com> Co-authored-by: Will Dollman <will.dollman@sourcegraph.com>
287 lines
9.0 KiB
Go
287 lines
9.0 KiB
Go
// Package shared provides the entrypoint to Sourcegraph's single docker
|
|
// image. It has functionality to setup the shared environment variables, as
|
|
// well as create the Procfile for goreman to run.
|
|
package shared
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/joho/godotenv"
|
|
"golang.org/x/sync/errgroup"
|
|
|
|
sglog "github.com/sourcegraph/log"
|
|
|
|
"github.com/sourcegraph/sourcegraph/cmd/server/internal/goreman"
|
|
"github.com/sourcegraph/sourcegraph/internal/database/postgresdsn"
|
|
"github.com/sourcegraph/sourcegraph/internal/env"
|
|
"github.com/sourcegraph/sourcegraph/internal/hostname"
|
|
"github.com/sourcegraph/sourcegraph/internal/version"
|
|
)
|
|
|
|
// FrontendInternalHost is the value of SRC_FRONTEND_INTERNAL.
|
|
const FrontendInternalHost = "127.0.0.1:3090"
|
|
|
|
// DefaultEnv is environment variables that will be set if not already set.
|
|
//
|
|
// If it is modified by an external package, it must be modified immediately on startup,
|
|
// before `shared.Main` is called.
|
|
var DefaultEnv = map[string]string{
|
|
// Sourcegraph services running in this container
|
|
"SRC_GIT_SERVERS": "127.0.0.1:3178",
|
|
"SEARCHER_URL": "http://127.0.0.1:3181",
|
|
"REPO_UPDATER_URL": "http://127.0.0.1:3182",
|
|
"QUERY_RUNNER_URL": "http://127.0.0.1:3183",
|
|
"SRC_SYNTECT_SERVER": "http://127.0.0.1:9238",
|
|
"SYMBOLS_URL": "http://127.0.0.1:3184",
|
|
"SRC_HTTP_ADDR": ":8080",
|
|
"SRC_HTTPS_ADDR": ":8443",
|
|
"SRC_FRONTEND_INTERNAL": FrontendInternalHost,
|
|
"GITHUB_BASE_URL": "http://127.0.0.1:3180", // points to github-proxy
|
|
|
|
"GRAFANA_SERVER_URL": "http://127.0.0.1:3370",
|
|
"PROMETHEUS_URL": "http://127.0.0.1:9090",
|
|
"OTEL_EXPORTER_OTLP_ENDPOINT": "", // disabled
|
|
|
|
// Limit our cache size to 100GB, same as prod. We should probably update
|
|
// searcher/symbols to ensure this value isn't larger than the volume for
|
|
// SYMBOLS_CACHE_DIR and SEARCHER_CACHE_DIR.
|
|
"SEARCHER_CACHE_SIZE_MB": "50000",
|
|
"SYMBOLS_CACHE_SIZE_MB": "50000",
|
|
|
|
// Used to differentiate between deployments on dev, Docker, and Kubernetes.
|
|
"DEPLOY_TYPE": "docker-container",
|
|
|
|
// enables the debug proxy (/-/debug)
|
|
"SRC_PROF_HTTP": "",
|
|
|
|
"LOGO": "t",
|
|
|
|
// TODO other bits
|
|
// * DEBUG LOG_REQUESTS https://github.com/sourcegraph/sourcegraph/issues/8458
|
|
}
|
|
|
|
// Set verbosity based on simple interpretation of env var to avoid external dependencies (such as
|
|
// on github.com/sourcegraph/sourcegraph/internal/env).
|
|
var verbose = os.Getenv("SRC_LOG_LEVEL") == "dbug" || os.Getenv("SRC_LOG_LEVEL") == "info"
|
|
|
|
// Main is the main server command function which is shared between Sourcegraph
|
|
// server's open-source and enterprise variant.
|
|
func Main() {
|
|
flag.Parse()
|
|
log.SetFlags(0)
|
|
liblog := sglog.Init(sglog.Resource{
|
|
Name: env.MyName,
|
|
Version: version.Version(),
|
|
InstanceID: hostname.Get(),
|
|
})
|
|
defer liblog.Sync()
|
|
|
|
logger := sglog.Scoped("server", "Sourcegraph server")
|
|
|
|
// Ensure CONFIG_DIR and DATA_DIR
|
|
|
|
// Load $CONFIG_DIR/env before we set any defaults
|
|
{
|
|
configDir := SetDefaultEnv("CONFIG_DIR", "/etc/sourcegraph")
|
|
err := os.MkdirAll(configDir, 0755)
|
|
if err != nil {
|
|
log.Fatalf("failed to ensure CONFIG_DIR exists: %s", err)
|
|
}
|
|
|
|
err = godotenv.Load(filepath.Join(configDir, "env"))
|
|
if err != nil && !os.IsNotExist(err) {
|
|
log.Fatalf("failed to load %s: %s", filepath.Join(configDir, "env"), err)
|
|
}
|
|
}
|
|
|
|
// Next persistence
|
|
{
|
|
SetDefaultEnv("SRC_REPOS_DIR", filepath.Join(DataDir, "repos"))
|
|
cacheDir := filepath.Join(DataDir, "cache")
|
|
SetDefaultEnv("SYMBOLS_CACHE_DIR", cacheDir)
|
|
SetDefaultEnv("SEARCHER_CACHE_DIR", cacheDir)
|
|
}
|
|
|
|
// Special case some convenience environment variables
|
|
if redis, ok := os.LookupEnv("REDIS"); ok {
|
|
SetDefaultEnv("REDIS_ENDPOINT", redis)
|
|
}
|
|
|
|
data, err := json.MarshalIndent(SrcProfServices, "", " ")
|
|
if err != nil {
|
|
log.Println("Failed to marshal default SRC_PROF_SERVICES")
|
|
} else {
|
|
SetDefaultEnv("SRC_PROF_SERVICES", string(data))
|
|
}
|
|
|
|
for k, v := range DefaultEnv {
|
|
SetDefaultEnv(k, v)
|
|
}
|
|
|
|
if v, _ := strconv.ParseBool(os.Getenv("ALLOW_SINGLE_DOCKER_CODE_INSIGHTS")); v {
|
|
AllowSingleDockerCodeInsights = true
|
|
}
|
|
|
|
// Now we put things in the right place on the FS
|
|
if err := copySSH(); err != nil {
|
|
// TODO There are likely several cases where we don't need SSH
|
|
// working, we shouldn't prevent setup in those cases. The main one
|
|
// that comes to mind is an ORIGIN_MAP which creates https clone URLs.
|
|
log.Println("Failed to setup SSH authorization:", err)
|
|
log.Fatal("SSH authorization required for cloning from your codehost. Please see README.")
|
|
}
|
|
if err := copyConfigs(); err != nil {
|
|
log.Fatal("Failed to copy configs:", err)
|
|
}
|
|
|
|
// TODO validate known_hosts contains all code hosts in config.
|
|
|
|
nginx, err := nginxProcFile()
|
|
if err != nil {
|
|
log.Fatal("Failed to setup nginx:", err)
|
|
}
|
|
|
|
postgresExporterLine := fmt.Sprintf(`postgres_exporter: env DATA_SOURCE_NAME="%s" postgres_exporter --config.file="/postgres_exporter.yaml" --log.level=%s`, postgresdsn.New("", "postgres", os.Getenv), convertLogLevel(os.Getenv("SRC_LOG_LEVEL")))
|
|
|
|
// TODO: This should be fixed properly.
|
|
// Tell `gitserver` that its `hostname` is what the others think of as gitserver hostnames.
|
|
gitserverLine := fmt.Sprintf(`gitserver: env HOSTNAME=%q gitserver`, os.Getenv("SRC_GIT_SERVERS"))
|
|
|
|
procfile := []string{
|
|
nginx,
|
|
`frontend: env CONFIGURATION_MODE=server frontend`,
|
|
gitserverLine,
|
|
`symbols: symbols`,
|
|
`searcher: searcher`,
|
|
`github-proxy: github-proxy`,
|
|
`worker: worker`,
|
|
`repo-updater: repo-updater`,
|
|
`syntax_highlighter: sh -c 'env QUIET=true ROCKET_ENV=production ROCKET_PORT=9238 ROCKET_LIMITS='"'"'{json=10485760}'"'"' ROCKET_SECRET_KEY='"'"'SeerutKeyIsI7releuantAndknvsuZPluaseIgnorYA='"'"' ROCKET_KEEP_ALIVE=0 ROCKET_ADDRESS='"'"'"127.0.0.1"'"'"' syntax_highlighter | grep -v "Rocket has launched" | grep -v "Warning: environment is"' | grep -v 'Configured for production'`,
|
|
postgresExporterLine,
|
|
}
|
|
procfile = append(procfile, ProcfileAdditions...)
|
|
|
|
if monitoringLines := maybeObservability(); len(monitoringLines) != 0 {
|
|
procfile = append(procfile, monitoringLines...)
|
|
}
|
|
|
|
if blobstoreLines := maybeBlobstore(logger); len(blobstoreLines) != 0 {
|
|
procfile = append(procfile, blobstoreLines...)
|
|
}
|
|
|
|
redisStoreLine, err := maybeRedisStoreProcFile()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
if redisStoreLine != "" {
|
|
procfile = append(procfile, redisStoreLine)
|
|
}
|
|
redisCacheLine, err := maybeRedisCacheProcFile()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
if redisCacheLine != "" {
|
|
procfile = append(procfile, redisCacheLine)
|
|
}
|
|
|
|
procfile = append(procfile, maybeZoektProcFile()...)
|
|
|
|
var (
|
|
postgresProcfile []string
|
|
restore, _ = strconv.ParseBool(os.Getenv("PGRESTORE"))
|
|
)
|
|
|
|
postgresLine, err := maybePostgresProcFile()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
if postgresLine != "" {
|
|
if restore {
|
|
// If in restore mode, only run PostgreSQL
|
|
procfile = []string{postgresLine}
|
|
} else {
|
|
postgresProcfile = append(postgresProcfile, postgresLine)
|
|
}
|
|
} else if restore {
|
|
log.Fatal("PGRESTORE is set but a local Postgres instance is not configured")
|
|
}
|
|
|
|
// Shutdown if any process dies
|
|
procDiedAction := goreman.Shutdown
|
|
if ignore, _ := strconv.ParseBool(os.Getenv("IGNORE_PROCESS_DEATH")); ignore {
|
|
// IGNORE_PROCESS_DEATH is an escape hatch so that sourcegraph/server
|
|
// keeps running in the case of a subprocess dying on startup. An
|
|
// example use case is connecting to postgres even though frontend is
|
|
// dying due to a bad migration.
|
|
procDiedAction = goreman.Ignore
|
|
}
|
|
|
|
runMigrations := !restore
|
|
run(procfile, postgresProcfile, runMigrations, procDiedAction)
|
|
}
|
|
|
|
func run(procfile, postgresProcfile []string, runMigrations bool, procDiedAction goreman.ProcDiedAction) {
|
|
if !runMigrations {
|
|
procfile = append(procfile, postgresProcfile...)
|
|
postgresProcfile = nil
|
|
}
|
|
|
|
group, _ := errgroup.WithContext(context.Background())
|
|
|
|
options := goreman.Options{
|
|
RPCAddr: "127.0.0.1:5005",
|
|
ProcDiedAction: procDiedAction,
|
|
}
|
|
startProcesses(group, "postgres", postgresProcfile, options)
|
|
|
|
if runMigrations {
|
|
// Run migrations before starting up the application but after
|
|
// starting any postgres instance within the server container.
|
|
runMigrator()
|
|
}
|
|
|
|
startProcesses(group, "all", procfile, options)
|
|
|
|
if err := group.Wait(); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func startProcesses(group *errgroup.Group, name string, procfile []string, options goreman.Options) {
|
|
if len(procfile) == 0 {
|
|
return
|
|
}
|
|
|
|
log.Printf("Starting %s processes", name)
|
|
group.Go(func() error { return goreman.Start([]byte(strings.Join(procfile, "\n")), options) })
|
|
}
|
|
|
|
func runMigrator() {
|
|
log.Println("Starting migrator")
|
|
|
|
schemas := []string{"frontend", "codeintel"}
|
|
if AllowSingleDockerCodeInsights {
|
|
schemas = append(schemas, "codeinsights")
|
|
}
|
|
|
|
for _, schemaName := range schemas {
|
|
e := execer{}
|
|
e.Command("migrator", "up", "-db", schemaName)
|
|
|
|
if err := e.Error(); err != nil {
|
|
pgPrintf("Migrating %s schema failed: %s", schemaName, err)
|
|
log.Fatal(err.Error())
|
|
}
|
|
}
|
|
|
|
log.Println("Migrated postgres schemas.")
|
|
}
|