Skip to content

Commit

Permalink
Patch plumbing of docker-compose UID/GID build args (erigontech#4527)
Browse files Browse the repository at this point in the history
* Patch plumbing of docker-compose UID/GID build args

* Fallback to 1000/1000 if DOCKER_(U|G)ID not set

* Revise README.md instructions for docker further

* Fix existing typo forc 'servie' -> 'service'

* Rename PUID/GUID -> UID/GID

* Specify user in erigon docker service

* Rely on .env instead of specifying :-1000

* Polish Makefile for docker use case

* one more helpful comment

* make docker should use UID/GID --build-arg

* Fix make docker and more fail-fast if envvar set incorrect

* mv .env->.env.example to not intefere existing workflows

* Specify envvars in docker CI

* Adjust validate_docker_build_args to permit non-erigon user

* Also run docker CI target on macos-11 os

* Add DOCKER_UID, DOCKER_GID in hooks/build

* Patch docker build arg validation for macos

* Add actions-setup-docker@master for macos

* Don't run automated test for docker macos

* Cleanup Makefile

* Comments, targets for erigon users

* More Makefile cleanup, debugging still

* Typo fix

* Create subdirs before calling ls

* Get rid of flaky validation

* DOCKER_UID, DOCKER_GID init to runner if not set

* Get rid of unnecessary variable for now

* Improved README based on new changes

* Proper uid/gid `make user_*` when no envars set

* Fix typo in Makefile comment

* Fix make docker as sudo user
  • Loading branch information
stoooops authored Jun 30, 2022
1 parent 5013be0 commit 74cf984
Show file tree
Hide file tree
Showing 8 changed files with 203 additions and 50 deletions.
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# host OS dedicated user (for docker especially)
ERIGON_USER=erigon

# UID, GID of user inside docker process which must exist also on host OS
DOCKER_UID=3473 # random number [1001, 10000] chosen arbitrarily for example
DOCKER_GID=3473 # can choose any valid #. 1000 tends to be taken by first user
8 changes: 7 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,10 @@ jobs:
- uses: actions/checkout@v3
with:
fetch-depth: 0 # fetch git tags for "git describe"
- run: make docker

- name: make docker
run: DOCKER_UID=$(id -u) DOCKER_GID=$(id -g) make docker

# check with root permissions, should be cached from previous build
- name: sudo make docker
run: sudo DOCKER_UID=$(id -u) DOCKER_GID=$(id -g) make docker
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,6 @@ go.work

/goerli

docker-compose.*.yml
docker-compose.*.yml
.env

36 changes: 24 additions & 12 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,29 +14,41 @@ RUN --mount=type=cache,target=/root/.cache \
FROM docker.io/library/alpine:3.15

RUN apk add --no-cache ca-certificates libstdc++ tzdata
# copy compiled artifacts from builder
COPY --from=builder /app/build/bin/* /usr/local/bin/

ARG PUID=1000
ARG PGID=1000
RUN adduser -H -u ${PUID} -g ${PGID} -D erigon
RUN mkdir -p /home/erigon
RUN mkdir -p /home/erigon/.local/share/erigon
RUN chown -R erigon:erigon /home/erigon

# Setup user and group
#
# from the perspective of the container, uid=1000, gid=1000 is a sensible choice
# (mimicking Ubuntu Server), but if caller creates a .env (example in repo root),
# these defaults will get overridden when make calls docker-compose
ARG UID=1000
ARG GID=1000
RUN adduser -D -u $UID -g $GID erigon
USER erigon

EXPOSE 8545 8551 8546 30303 30303/udp 42069 42069/udp 8080 9090 6060
RUN mkdir -p ~/.local/share/erigon

EXPOSE 8545 \
8551 \
8546 \
30303 \
30303/udp \
42069 \
42069/udp \
8080 \
9090 \
6060

# https://github.com/opencontainers/image-spec/blob/main/annotations.md
ARG BUILD_DATE
ARG VCS_REF
ARG VERSION
LABEL org.label-schema.build-date=$BUILD_DATE \
org.label-schema.name="Erigon" \
org.label-schema.description="Erigon Ethereum Client" \
org.label-schema.name="Erigon" \
org.label-schema.schema-version="1.0" \
org.label-schema.url="https://torquem.ch" \
org.label-schema.vcs-ref=$VCS_REF \
org.label-schema.vcs-url="https://github.com/ledgerwatch/erigon.git" \
org.label-schema.vendor="Torquem" \
org.label-schema.version=$VERSION \
org.label-schema.schema-version="1.0"
org.label-schema.version=$VERSION
79 changes: 65 additions & 14 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
GO = go
GO = go # if using docker, should not need to be installed/linked
GOBIN = $(CURDIR)/build/bin
UNAME = $(shell uname) # Supported: Darwin, Linux
DOCKER := $(shell command -v docker 2> /dev/null)

GIT_COMMIT ?= $(shell git rev-list -1 HEAD)
GIT_BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD)
GIT_TAG ?= $(shell git describe --tags '--match=v*' --dirty)
DOCKER_UID ?= 1000
DOCKER_PID ?= 1000
ERIGON_USER ?= erigon
# if using volume-mounting data dir, then must exist on host OS
DOCKER_UID ?= $(shell id -u)
DOCKER_GID ?= $(shell id -g)
DOCKER_TAG ?= thorax/erigon:latest

CGO_CFLAGS := $(shell $(GO) env CGO_CFLAGS) # don't loose default
# Variables below for building on host OS, and are ignored for docker
#
# Pipe error below to /dev/null since Makefile structure kind of expects
# Go to be available, but with docker it's not strictly necessary
CGO_CFLAGS := $(shell $(GO) env CGO_CFLAGS 2>/dev/null) # don't lose default
CGO_CFLAGS += -DMDBX_FORCE_ASSERTIONS=1 # Enable MDBX's asserts by default in 'devel' branch and disable in 'stable'
CGO_CFLAGS := CGO_CFLAGS="$(CGO_CFLAGS)"
DBG_CGO_CFLAGS += -DMDBX_DEBUG=1

GO_MINOR_VERSION = $(shell $(GO) version | cut -c 16-17)
BUILD_TAGS = nosqlite,noboltdb
PACKAGE = github.com/ledgerwatch/erigon

Expand All @@ -27,27 +34,41 @@ GOTEST = GODEBUG=cgocheck=0 $(GO) test $(GO_FLAGS) ./... -p 2
default: all

go-version:
@if [ $(GO_MINOR_VERSION) -lt 18 ]; then \
@if [ $(shell $(GO) version | cut -c 16-17) -lt 18 ]; then \
echo "minimum required Golang version is 1.18"; \
exit 1 ;\
fi

docker: git-submodules
DOCKER_BUILDKIT=1 docker build -t ${DOCKER_TAG} \
validate_docker_build_args:
@echo "Docker build args:"
@echo " DOCKER_UID: $(DOCKER_UID)"
@echo " DOCKER_GID: $(DOCKER_GID)\n"
@echo "Ensuring host OS user exists with specified UID/GID..."
@if [ "$(UNAME)" = "Darwin" ]; then \
dscl . list /Users UniqueID | grep "$(DOCKER_UID)"; \
elif [ "$(UNAME)" = "Linux" ]; then \
cat /etc/passwd | grep "$(DOCKER_UID):$(DOCKER_GID)"; \
fi
@echo "✔️ host OS user exists: $(shell id -nu $(DOCKER_UID))"

docker: validate_docker_build_args git-submodules
DOCKER_BUILDKIT=1 $(DOCKER) build -t ${DOCKER_TAG} \
--build-arg "BUILD_DATE=$(shell date -Iseconds)" \
--build-arg VCS_REF=${GIT_COMMIT} \
--build-arg VERSION=${GIT_TAG} \
--build-arg PUID=${DOCKER_UID} \
--build-arg PGID=${DOCKER_PID} \
--build-arg UID=${DOCKER_UID} \
--build-arg GID=${DOCKER_GID} \
${DOCKER_FLAGS} \
.

xdg_data_home := ~/.local/share
ifdef XDG_DATA_HOME
xdg_data_home = $(XDG_DATA_HOME)
endif
docker-compose:
mkdir -p $(xdg_data_home)/erigon $(xdg_data_home)/erigon-grafana $(xdg_data_home)/erigon-prometheus; \
xdg_data_home_subdirs = $(xdg_data_home)/erigon $(xdg_data_home)/erigon-grafana $(xdg_data_home)/erigon-prometheus

docker-compose: validate_docker_build_args
mkdir -p $(xdg_data_home_subdirs)
docker-compose up

# debug build allows see C stack traces, run it with GOTRACEBACK=crash. You don't need debug build for C pit for profiling. To profile C code use SETCGOTRCKEBACK=1
Expand Down Expand Up @@ -146,13 +167,43 @@ bindings:
prometheus:
docker-compose up prometheus grafana


escape:
cd $(path) && go test -gcflags "-m -m" -run none -bench=BenchmarkJumpdest* -benchmem -memprofile mem.out

git-submodules:
@[ -d ".git" ] || (echo "Not a git repository" && exit 1)
@echo "Updating git submodules"
@# Dockerhub using ./hooks/post-checkout to set submodules, so this line will fail on Dockerhub
@git submodule sync --quiet --recursive
@# these lines will also fail if ran as root in a non-root user's checked out repository
@git submodule sync --quiet --recursive || true
@git submodule update --quiet --init --recursive --force || true

# since DOCKER_UID, DOCKER_GID are default initialized to the current user uid/gid,
# we need separate envvars to facilitate creation of the erigon user on the host OS.
ERIGON_USER_UID ?= 3473
ERIGON_USER_GID ?= 3473
ERIGON_USER_XDG_DATA_HOME ?= ~$(ERIGON_USER)/.local/share

# create "erigon" user
user_linux:
ifdef DOCKER
sudo groupadd -f docker
endif
sudo addgroup --gid $(ERIGON_USER_GID) $(ERIGON_USER) 2> /dev/null || true
sudo adduser --disabled-password --gecos '' --uid $(ERIGON_USER_UID) --gid $(ERIGON_USER_GID) $(ERIGON_USER) 2> /dev/null || true
sudo mkhomedir_helper $(ERIGON_USER)
echo 'export PATH=$$PATH:/usr/local/go/bin' | sudo -u $(ERIGON_USER) tee /home/$(ERIGON_USER)/.bash_aliases >/dev/null
ifdef DOCKER
sudo usermod -aG docker $(ERIGON_USER)
endif
sudo -u $(ERIGON_USER) mkdir -p ~$(ERIGON_USER_XDG_DATA_HOME)

# create "erigon" user
user_macos:
sudo dscl . -create /Users/$(ERIGON_USER)
sudo dscl . -create /Users/$(ERIGON_USER) UserShell /bin/bash
sudo dscl . -list /Users UniqueID | grep $(ERIGON_USER) | grep $(ERIGON_USER_UID) || sudo dscl . -create /Users/$(ERIGON_USER) UniqueID $(ERIGON_USER_UID)
sudo dscl . -create /Users/$(ERIGON_USER) PrimaryGroupID $(ERIGON_USER_GID)
sudo dscl . -create /Users/$(ERIGON_USER) NFSHomeDirectory /Users/$(ERIGON_USER)
sudo dscl . -append /Groups/admin GroupMembership $(ERIGON_USER)
sudo -u $(ERIGON_USER) mkdir -p ~$(ERIGON_USER_XDG_DATA_HOME)
82 changes: 70 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,11 @@ System Requirements

* Goerli Full node (see `--prune*` flags): 189GB on Beta, 114GB on Alpha (April 2022).

* BSC Archive: 7TB. BSC Full: 1TB.
* BSC Archive: 7TB. BSC Full: 1TB.

* Polygon Mainnet Archive: 5TB. Polygon Mumbai Archive: 1TB.

SSD or NVMe. Do not recommend HDD - on HDD Erigon will always stay N blocks behind chain tip, but not fall behind.
SSD or NVMe. Do not recommend HDD - on HDD Erigon will always stay N blocks behind chain tip, but not fall behind.
Bear in mind that SSD performance deteriorates when close to capacity.

RAM: >=16GB, 64-bit architecture, [Golang version >= 1.18](https://golang.org/doc/install), GCC 10+
Expand All @@ -71,7 +71,7 @@ make erigon
./build/bin/erigon
```

Default `--snapshots=true` for `mainnet`, `goerli`, `bsc`. Other networks now have default `--snapshots=false`. Increase download speed by flag `--torrent.download.rate=20mb`. <code>🔬 See [Downloader docs](./cmd/downloader/readme.md)</code>
Default `--snapshots=true` for `mainnet`, `goerli`, `bsc`. Other networks now have default `--snapshots=false`. Increase download speed by flag `--torrent.download.rate=20mb`. <code>🔬 See [Downloader docs](./cmd/downloader/readme.md)</code>

Use `--datadir` to choose where to store data.

Expand Down Expand Up @@ -190,18 +190,18 @@ In order to establish a secure connection between the Consensus Layer and the Ex
The JWT secret key will be present in the datadir by default under the name of `jwt.hex` and its path can be specified with the flag `--authrpc.jwtsecret`.

This piece of info needs to be specified in the Consensus Layer as well in order to establish connection successfully. More information can be found [here](https://github.com/ethereum/execution-apis/blob/main/src/engine/authentication.md)

### Multiple Instances / One Machine

Define 5 flags to avoid conflicts: `--datadir --port --http.port --torrent.port --private.api.addr`. Example of multiple chains on the same machine:

```
# mainnet
./build/bin/erigon --datadir="<your_mainnet_data_path>" --chain=mainnet --port=30303 --http.port=8545 --torrent.port=42069 --private.api.addr=127.0.0.1:9090 --http --ws --http.api=eth,debug,net,trace,web3,erigon
./build/bin/erigon --datadir="<your_mainnet_data_path>" --chain=mainnet --port=30303 --http.port=8545 --torrent.port=42069 --private.api.addr=127.0.0.1:9090 --http --ws --http.api=eth,debug,net,trace,web3,erigon
# rinkeby
./build/bin/erigon --datadir="<your_rinkeby_data_path>" --chain=rinkeby --port=30304 --http.port=8546 --torrent.port=42068 --private.api.addr=127.0.0.1:9091 --http --ws --http.api=eth,debug,net,trace,web3,erigon
./build/bin/erigon --datadir="<your_rinkeby_data_path>" --chain=rinkeby --port=30304 --http.port=8546 --torrent.port=42068 --private.api.addr=127.0.0.1:9091 --http --ws --http.api=eth,debug,net,trace,web3,erigon
```

Quote your path if it has spaces.
Expand All @@ -210,7 +210,7 @@ Quote your path if it has spaces.
<code> 🔬 Detailed explanation is [DEV_CHAIN](/DEV_CHAIN.md).</code>

Key features
============
============

<code>🔬 See more
detailed [overview of functionality and current limitations](https://ledgerwatch.github.io/turbo_geth_release.html). It
Expand Down Expand Up @@ -261,11 +261,11 @@ Examples of stages are:

### JSON-RPC daemon

Most of Erigon's components (sentry, txpool, snapshots downloader, can work inside Erigon and as independent process.
Most of Erigon's components (sentry, txpool, snapshots downloader, can work inside Erigon and as independent process.

To enable built-in RPC server: `--http` and `--ws` (sharing same port with http)

Run RPCDaemon as separated process: this daemon can use local DB (with running Erigon or on snapshot of a database) or remote DB (run on another server). <code>🔬 See [RPC-Daemon docs](./cmd/rpcdaemon/README.md)</code>
Run RPCDaemon as separated process: this daemon can use local DB (with running Erigon or on snapshot of a database) or remote DB (run on another server). <code>🔬 See [RPC-Daemon docs](./cmd/rpcdaemon/README.md)</code>

#### **For remote DB**

Expand All @@ -290,13 +290,71 @@ For a details on the implementation status of each
command, [see this table](./cmd/rpcdaemon/README.md#rpc-implementation-status).

### Run all components by docker-compose
Docker allows for building and running Erigon via containers. This alleviates the need for installing build dependencies onto the host OS.

#### Optional: Setup dedicated user
User UID/GID need to be synchronized between the host OS and container so files are written with correct permission.

You may wish to setup a dedicated user/group on the host OS, in which case the following `make` targets are available.
```sh
# create "erigon" user
make user_linux
# or
make user_macos
```

#### Environment Variables
There is a `.env.example` file in the root of the repo.
* `DOCKER_UID` - The UID of the docker user
* `DOCKER_GID` - The GID of the docker user
* `XDG_DATA_HOME` - The data directory which will be mounted to the docker containers

If not specified, the UID/GID will use the current user.

A good choice for `XDG_DATA_HOME` is to use the `~erigon/.ethereum` directory created by helper targets `make user_linux` or `make user_macos`.

#### Check: Permissions
In all cases, `XDG_DATA_HOME` (specified or default) must be writeable by the user UID/GID in docker, which will be determined by the `DOCKER_UID` and `DOCKER_GID` at build time.

If a build or service startup is failing due to permissions, check that all the directories, UID, and GID controlled by these environment variables are correct.

#### Run
Next command starts: Erigon on port 30303, rpcdaemon on port 8545, prometheus on port 9090, and grafana on port 3000.

```sh
#
# Will mount ~/.local/share/erigon to /home/erigon/.local/share/erigon inside container
#
make docker-compose

#
# or
XDG_DATA_HOME=/preferred/data/folder DOCKER_BUILDKIT=1 COMPOSE_DOCKER_CLI_BUILD=1 make docker-compose
#
# if you want to use a custom data directory
# or, if you want to use different uid/gid for a dedicated user
#
# To solve this, pass in the uid/gid parameters into the container.
#
# DOCKER_UID: the user id
# DOCKER_GID: the group id
# XDG_DATA_HOME: the data directory (default: ~/.local/share)
#
# Note: /preferred/data/folder must be read/writeable on host OS by user with UID/GID given
# if you followed above instructions
#
# Note: uid/gid syntax below will automatically use uid/gid of running user so this syntax
# is intended to be ran via the dedicated user setup earlier
#
DOCKER_UID=$(id -u) DOCKER_GID=$(id -g) XDG_DATA_HOME=/preferred/data/folder DOCKER_BUILDKIT=1 COMPOSE_DOCKER_CLI_BUILD=1 make docker-compose

#
# if you want to run the docker, but you are not logged in as the $ERIGON_USER
# then you'll need to adjust the syntax above to grab the correct uid/gid
#
# To run the command via another user, use
#
ERIGON_USER=erigon
sudo -u ${ERIGON_USER} DOCKER_UID=$(id -u ${ERIGON_USER}) DOCKER_GID=$(id -g ${ERIGON_USER}) XDG_DATA_HOME=~${ERIGON_USER}/.ethereum DOCKER_BUILDKIT=1 COMPOSE_DOCKER_CLI_BUILD=1 make docker-compose
```

Makefile creates the initial directories for erigon, prometheus and grafana. The PID namespace is shared between erigon
Expand Down Expand Up @@ -382,7 +440,7 @@ Reserved for future use: **gRPC ports**: `9092` consensus engine, `9093` snapsho
run `go tool pprof -png http://127.0.0.1:6060/debug/pprof/profile\?seconds\=20 > cpu.png`
- Get RAM profiling: add `--pprof flag`
run `go tool pprof -inuse_space -png http://127.0.0.1:6060/debug/pprof/heap > mem.png`

### How to run local devnet?
<code> 🔬 Detailed explanation is [here](/DEV_CHAIN.md).</code>

Expand Down Expand Up @@ -455,7 +513,7 @@ Application

`htop` on column `res` shows memory of "App + OS used to hold page cache for given App", but it's not informative,
because if `htop` says that app using 90% of memory you still can run 3 more instances of app on the same machine -
because most of that `90%` is "OS pages cache".
because most of that `90%` is "OS pages cache".
OS automatically free this cache any time it needs memory. Smaller "page cache size" may not impact performance of
Erigon at all.

Expand Down
Loading

0 comments on commit 74cf984

Please sign in to comment.