Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basics around deployment #5

Merged
merged 12 commits into from
Oct 6, 2023
Merged
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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Normally we would install a local resolver with its own caching in place but it wouldn't be needed in any case with this, iirc?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, if we want to drop this and just have the OS take care of it that's also totally fine. Leaving it for now though

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

Check warning on line 38 in dns_cache.go

View check run for this annotation

Codecov / codecov/patch

dns_cache.go#L20-L38

Added lines #L20 - L38 were not covered by tests
}(cache)

return cache

Check warning on line 41 in dns_cache.go

View check run for this annotation

Codecov / codecov/patch

dns_cache.go#L41

Added line #L41 was not covered by tests
}

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

Check warning on line 53 in dns_cache.go

View check run for this annotation

Codecov / codecov/patch

dns_cache.go#L45-L53

Added lines #L45 - L53 were not covered by tests
// 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

Check warning on line 59 in dns_cache.go

View check run for this annotation

Codecov / codecov/patch

dns_cache.go#L55-L59

Added lines #L55 - L59 were not covered by tests
}
}
return

Check warning on line 62 in dns_cache.go

View check run for this annotation

Codecov / codecov/patch

dns_cache.go#L62

Added line #L62 was not covered by tests
}

func (cdns *cachedDNS) Close() error {
cdns.refresher.Stop()
return nil

Check warning on line 67 in dns_cache.go

View check run for this annotation

Codecov / codecov/patch

dns_cache.go#L65-L67

Added lines #L65 - L67 were not covered by tests
}
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
102 changes: 102 additions & 0 deletions docs/environment-variables.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# 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 as temporary solution for IPNS Record routing until [IPIP-351](https://github.com/ipfs/specs/pull/351) ships with Kubo 0.19,
and we also redirect some legacy `/api/v0` commands that need to be handled on `ipfs.io`.
aschmahmann marked this conversation as resolved.
Show resolved Hide resolved
aschmahmann marked this conversation as resolved.
Show resolved Hide resolved

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

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)

Check warning on line 22 in gc.go

View check run for this annotation

Codecov / codecov/patch

gc.go#L22

Added line #L22 was not covered by tests
}

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