Skip to content

Commit

Permalink
Basics around deployment (#5)
Browse files Browse the repository at this point in the history
* import configuration from bifrost-gateway
* add Kubo RPC URL env vars and /api/v0 support via
  delegation/forwarding
* add support for executing routing locally if no delegated endpoint
* add DNS resolution caching layer
* add tracing and metrics
* add debugging and metrics endpoints
* add Dockerfile
* switched cli flags to only expose the port and always run on localhost
* add more standard ignores to .gitignore
* add http user agent
  • Loading branch information
aschmahmann authored Oct 6, 2023
1 parent 64c73a2 commit dad528b
Show file tree
Hide file tree
Showing 18 changed files with 1,652 additions and 145 deletions.
16 changes: 16 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,19 @@ rainbow
libp2p.key
blockstore
datastore

# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib

# Test binary, built with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# Dependency directories (remove the comment below to include it)
# vendor/
46 changes: 46 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
FROM --platform=${BUILDPLATFORM:-linux/amd64} golang:1.20-bullseye AS builder
# This builds rainbow

ARG TARGETPLATFORM TARGETOS TARGETARCH

ENV GOPATH /go
ENV SRC_PATH $GOPATH/src/github.com/ipfs/rainbow
ENV GO111MODULE on
ENV GOPROXY https://proxy.golang.org

COPY go.* $SRC_PATH/
WORKDIR $SRC_PATH
RUN go mod download

COPY . $SRC_PATH
RUN git config --global --add safe.directory /go/src/github.com/ipfs/rainbow

RUN --mount=target=. \
--mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg \
CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o $GOPATH/bin/rainbow

#------------------------------------------------------
FROM alpine:3.18

# This runs rainbow

# Instal binaries for $TARGETARCH
RUN apk add --no-cache tini su-exec ca-certificates

ENV GOPATH /go
ENV SRC_PATH $GOPATH/src/github.com/ipfs/rainbow
ENV RAINBOW_GATEWAY_PATH /data/rainbow
ENV KUBO_RPC_URL https://node0.delegate.ipfs.io,https://node1.delegate.ipfs.io,https://node2.delegate.ipfs.io,https://node3.delegate.ipfs.io

COPY --from=builder $GOPATH/bin/rainbow /usr/local/bin/rainbow
COPY --from=builder $SRC_PATH/docker/entrypoint.sh /usr/local/bin/entrypoint.sh

RUN mkdir -p $RAINBOW_GATEWAY_PATH && \
adduser -D -h $RAINBOW_GATEWAY_PATH -u 1000 -G users ipfs && \
chown ipfs:users $RAINBOW_GATEWAY_PATH
VOLUME $RAINBOW_GATEWAY_PATH

ENTRYPOINT ["/sbin/tini", "--", "/usr/local/bin/entrypoint.sh"]

CMD ["--gateway-port", "8080", "--api-port", "8081"]
68 changes: 68 additions & 0 deletions dns_cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package main

import (
"context"
"net"
"time"

"github.com/rs/dnscache"
)

// How often should we check for successful updates to cached entries
const dnsCacheRefreshInterval = 5 * time.Minute

// Local DNS cache because in this world things are ephemeral
type cachedDNS struct {
resolver *dnscache.Resolver
refresher *time.Ticker
}

func newCachedDNS(refreshInterval time.Duration) *cachedDNS {
cache := &cachedDNS{
resolver: &dnscache.Resolver{},
refresher: time.NewTicker(refreshInterval),
}

// Configure DNS cache to not remove stale records to protect gateway from
// catastrophic failures like https://github.com/ipfs/bifrost-gateway/issues/34
options := dnscache.ResolverRefreshOptions{}
options.ClearUnused = false
options.PersistOnFailure = true

// Every refreshInterval we check for updates, but if there is
// none, or if domain disappears, we keep the last cached version
go func(cdns *cachedDNS) {
defer cdns.refresher.Stop()
for range cdns.refresher.C {
cdns.resolver.RefreshWithOptions(options)
}
}(cache)

return cache
}

// dialWithCachedDNS implements DialContext that uses cachedDNS
func (cdns *cachedDNS) dialWithCachedDNS(ctx context.Context, network string, addr string) (conn net.Conn, err error) {
host, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
ips, err := cdns.resolver.LookupHost(ctx, host)
if err != nil {
return nil, err
}
// Try all IPs returned by DNS
for _, ip := range ips {
var dialer net.Dialer
conn, err = dialer.DialContext(ctx, network, net.JoinHostPort(ip, port))
if err == nil {
break
}
}
return
}

func (cdns *cachedDNS) Close() error {
cdns.refresher.Stop()
return nil
}
18 changes: 18 additions & 0 deletions docker/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/bin/sh

set -e
user=ipfs

if [ -n "$DOCKER_DEBUG" ]; then
set -x
fi

if [ `id -u` -eq 0 ]; then
echo "Changing user to $user"
exec su-exec "$user" "$0" $@
fi

# Only ipfs user can get here
rainbow --version

exec rainbow $@
56 changes: 56 additions & 0 deletions docker/get-docker-tags.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/usr/bin/env bash

# get-docker-tags.sh produces Docker tags for the current build
#
# Usage:
# ./get-docker-tags.sh <build number> <git commit sha1> <git branch name> [git tag name]
#
# Example:
#
# # get tag for the main branch
# ./get-docker-tags.sh $(date -u +%F) testingsha main
#
# # get tag for a release tag
# ./get-docker-tags.sh $(date -u +%F) testingsha release v0.5.0
#
# # Serving suggestion in CI
# ./get-docker-tags.sh $(date -u +%F) "$CI_SHA1" "$CI_BRANCH" "$CI_TAG"
#
set -euo pipefail

if [[ $# -lt 1 ]] ; then
echo 'At least 1 arg required.'
echo 'Usage:'
echo './get-docker-tags.sh <build number> [git commit sha1] [git branch name] [git tag name]'
exit 1
fi

BUILD_NUM=$1
GIT_SHA1=${2:-$(git rev-parse HEAD)}
GIT_SHA1_SHORT=$(echo "$GIT_SHA1" | cut -c 1-7)
GIT_BRANCH=${3:-$(git symbolic-ref -q --short HEAD || echo "unknown")}
GIT_TAG=${4:-$(git describe --tags --exact-match 2> /dev/null || echo "")}

IMAGE_NAME=${IMAGE_NAME:-ipfs/rainbow}

echoImageName () {
local IMAGE_TAG=$1
echo "$IMAGE_NAME:$IMAGE_TAG"
}

if [[ $GIT_TAG =~ ^v[0-9]+\.[0-9]+\.[0-9]+-rc ]]; then
echoImageName "$GIT_TAG"

elif [[ $GIT_TAG =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echoImageName "$GIT_TAG"
echoImageName "latest"

elif [ "$GIT_BRANCH" = "main" ]; then
echoImageName "main-${BUILD_NUM}-${GIT_SHA1_SHORT}"
echoImageName "main-latest"


else
echo "Nothing to do. No docker tag defined for branch: $GIT_BRANCH, tag: $GIT_TAG"

fi
101 changes: 101 additions & 0 deletions docs/environment-variables.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Rainbow Environment Variables

`rainbow` ships with some implicit defaults that can be adjusted via env variables below.

- [Configuration](#configuration)
- [`KUBO_RPC_URL`](#kubo_rpc_url)
- [Logging](#logging)
- [`GOLOG_LOG_LEVEL`](#golog_log_level)
- [`GOLOG_LOG_FMT`](#golog_log_fmt)
- [`GOLOG_FILE`](#golog_file)
- [`GOLOG_TRACING_FILE`](#golog_tracing_file)
- [Testing](#testing)
- [`GATEWAY_CONFORMANCE_TEST`](#gateway_conformance_test)
- [`IPFS_NS_MAP`](#ipfs_ns_map)

## Configuration


### `KUBO_RPC_URL`

Default: see `DefaultKuboRPC`

Single URL or a comma separated list of RPC endpoints that provide `/api/v0` from Kubo.

We use this to redirect some legacy `/api/v0` commands that need to be handled on `ipfs.io`.

## Logging

### `GOLOG_LOG_LEVEL`

Specifies the log-level, both globally and on a per-subsystem basis. Level can
be one of:

* `debug`
* `info`
* `warn`
* `error`
* `dpanic`
* `panic`
* `fatal`

Per-subsystem levels can be specified with `subsystem=level`. One global level
and one or more per-subsystem levels can be specified by separating them with
commas.

Default: `error`

Example:

```console
GOLOG_LOG_LEVEL="error,bifrost-gateway=debug,caboose=debug" bifrost-gateway
```

### `GOLOG_LOG_FMT`

Specifies the log message format. It supports the following values:

- `color` -- human readable, colorized (ANSI) output
- `nocolor` -- human readable, plain-text output.
- `json` -- structured JSON.

For example, to log structured JSON (for easier parsing):

```bash
export GOLOG_LOG_FMT="json"
```
The logging format defaults to `color` when the output is a terminal, and
`nocolor` otherwise.

### `GOLOG_FILE`

Sets the file to which the Bifrost Gateway logs. By default, the Bifrost Gateway
logs to the standard error output.

### `GOLOG_TRACING_FILE`

Sets the file to which the Bifrost Gateway sends tracing events. By default,
tracing is disabled.

Warning: Enabling tracing will likely affect performance.


## Testing

### `GATEWAY_CONFORMANCE_TEST`

Setting to `true` enables support for test fixtures required by [ipfs/gateway-conformance](https://github.com/ipfs/gateway-conformance) test suite.

### `IPFS_NS_MAP`

Adds static namesys records for deterministic tests and debugging.
Useful for testing `/ipns/` support without having to do real IPNS/DNS lookup.

Example:

```console
$ IPFS_NS_MAP="dnslink-test1.example.com:/ipfs/bafkreicysg23kiwv34eg2d7qweipxwosdo2py4ldv42nbauguluen5v6am,dnslink-test2.example.com:/ipns/dnslink-test1.example.com" ./gateway-binary
...
$ curl -is http://127.0.0.1:8081/dnslink-test2.example.com/ | grep Etag
Etag: "bafkreicysg23kiwv34eg2d7qweipxwosdo2py4ldv42nbauguluen5v6am"
```
2 changes: 1 addition & 1 deletion gc.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func (nd *Node) GC(ctx context.Context, todelete int64) error {

size, err := nd.blockstore.GetSize(ctx, k)
if err != nil {
log.Warnf("failed to get size for block we are about to delete: %s", err)
goLog.Warnf("failed to get size for block we are about to delete: %s", err)
}

if err := nd.blockstore.DeleteBlock(ctx, k); err != nil {
Expand Down
Loading

0 comments on commit dad528b

Please sign in to comment.