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

feat(docker): /container-init.d for advanced initialization #6577

Merged
merged 9 commits into from
Apr 12, 2022
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ LABEL maintainer="Steven Allen <steven@stebalien.com>"
ENV SRC_DIR /go-ipfs
COPY --from=0 $SRC_DIR/cmd/ipfs/ipfs /usr/local/bin/ipfs
COPY --from=0 $SRC_DIR/bin/container_daemon /usr/local/bin/start_ipfs
COPY --from=0 $SRC_DIR/bin/container_init_run /usr/local/bin/container_init_run
COPY --from=0 /tmp/su-exec/su-exec-static /sbin/su-exec
COPY --from=0 /tmp/tini /sbin/tini
COPY --from=0 /bin/fusermount /usr/local/bin/fusermount
Expand Down Expand Up @@ -93,6 +94,10 @@ RUN mkdir -p $IPFS_PATH \
RUN mkdir /ipfs /ipns \
&& chown ipfs:users /ipfs /ipns

# Create the init scripts directory
RUN mkdir /container-init.d \
&& chown ipfs:users /container-init.d

# Expose the fs-repo as a volume.
# start_ipfs initializes an fs-repo if none is mounted.
# Important this happens after the USER directive so permissions are correct.
Expand Down
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,48 @@ Basic proof of 'ipfs working' locally:
# QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o
ipfs cat <that hash>

To perform a custom initialization and configuration step, one can mount `.sh` scripts to a `/container-init.d` directory, or mount the entire directory. These scripts are executed if they have `+x` permission, otherwise they are just sourced. Executed scripts can exit with error and abort the initialization process. The scripts may also depend on environment variables set by the `docker run` command or in `docker-compose.yml`. The custom initialization step is **only** performed during IPFS initialization, **after** `ipfs init`, **after** after the swarm key is copied to the IPFS data directory, and **before** the daemon is started. This also enables custom containers to layer extra initialization steps to the base image by adding more scripts to `/container-init.d`.
Copy link
Member

Choose a reason for hiding this comment

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

@guseggert @thattommyhall my understanding is that we already run scripts from /container-init.d every time, so we just need to document that + emphasize that scripts should be written in idempotent way.

Would below work?

Suggested change
To perform a custom initialization and configuration step, one can mount `.sh` scripts to a `/container-init.d` directory, or mount the entire directory. These scripts are executed if they have `+x` permission, otherwise they are just sourced. Executed scripts can exit with error and abort the initialization process. The scripts may also depend on environment variables set by the `docker run` command or in `docker-compose.yml`. The custom initialization step is **only** performed during IPFS initialization, **after** `ipfs init`, **after** after the swarm key is copied to the IPFS data directory, and **before** the daemon is started. This also enables custom containers to layer extra initialization steps to the base image by adding more scripts to `/container-init.d`.
To perform a custom initialization or configuration step, one can mount `.sh` scripts to a `/container-init.d` directory, or mount the entire directory. These scripts are executed if they have `+x` permission, otherwise they are just sourced. Executed scripts can exit with error and abort the initialization process. The scripts may also depend on environment variables set by the `docker run` command or in `docker-compose.yml`. **The custom initialization steps from `/container-init.d` are run every time, and should be idempotent.** When run the first time during IPFS initialization, custom scripts run **after** `ipfs init`, **after** the swarm key is copied to the IPFS data directory, and **before** the daemon is started for the very first time. This also enables custom containers to layer extra initialization steps to the base image by adding more scripts to `/container-init.d`.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah it's a good distinction to make, but the prose of this paragraph bothers me a little, let me rewrite it.

Copy link
Contributor

@guseggert guseggert Apr 11, 2022

Choose a reason for hiding this comment

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

I also don't think this advanced stuff is appropriate for the "getting started" section of the readme. And that section is also not docker-specific

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm going to just remove this and add it to the IPFS Docker documentation , which is already linked to from the readme.

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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


This example initializes a peer in a private network, without any communication with the default bootstrap nodes because it is performed before the daemon starts:

```bash

# This is the initialization script
TEST_NAME=00-test.sh
TEST_SCRIPT=`pwd`/$TEST_NAME

# The script will add this node when creating the container
PRIV_PEER_IP=127.0.0.1
PRIV_PEER_ID=QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ

# For instructional purposes, create the initialization script here,
# but this may be part of a suite of scripts maintained by the IT team,
# or created inside a custom Dockerfile.
cat >$TEST_SCRIPT <<EOF
echo Removing boostraps...
ipfs bootstrap rm all || exit 1
echo Adding custom boostrap from environment...
MULTIADDR=/ip4/\$PRIV_PEER_IP/tcp/4001/ipfs/\$PRIV_PEER_ID
ipfs bootstrap add \$MULTIADDR || exit 1
exit 0
EOF

# Set the script as executable
chmod +x $TEST_SCRIPT

# Initialize the container with the custom scripted mounted.
# It is also possible to mount the entire /container-init.d
# rather than a single script, if needed
docker run -d --name ipfs_host \
-e PRIV_PEER_IP=$PRIV_PEER_IP \
-e PRIV_PEER_ID=$PRIV_PEER_ID \
-v $TEST_SCRIPT:/container-init.d/$TEST_NAME \
-p 4001:4001 \
-p 127.0.0.1:8080:8080 \
-p 127.0.0.1:5001:5001 \
ipfs/go-ipfs:latest
```

### Troubleshooting

If you have previously installed IPFS before and you are running into problems getting a newer version to work, try deleting (or backing up somewhere else) your IPFS config directory (~/.ipfs by default) and rerunning `ipfs init`. This will reinitialize the config file to its defaults and clear out the local datastore of any bad entries.
Expand Down
19 changes: 9 additions & 10 deletions bin/container_daemon
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
#!/bin/sh
set -e

user=ipfs
repo="$IPFS_PATH"

if [ `id -u` -eq 0 ]; then
if [ "$(id -u)" -eq 0 ]; then
echo "Changing user to $user"
# ensure folder is writable
su-exec "$user" test -w "$repo" || chown -R -- "$user" "$repo"
Expand All @@ -14,14 +15,11 @@ fi
# 2nd invocation with regular user
ipfs version


if [ -e "$repo/config" ]; then
echo "Found IPFS fs-repo at $repo"
else
case "$IPFS_PROFILE" in
"") INIT_ARGS="" ;;
*) INIT_ARGS="--profile=$IPFS_PROFILE" ;;
esac
ipfs init $INIT_ARGS
ipfs init ${IPFS_PROFILE:+"--profile=$IPFS_PROFILE"}
ipfs config Addresses.API /ip4/0.0.0.0/tcp/5001
ipfs config Addresses.Gateway /ip4/0.0.0.0/tcp/8080

Expand All @@ -31,9 +29,9 @@ else
SWARM_KEY_PERM=0400

# Create a swarm key from a given environment variable
if [ ! -z "$IPFS_SWARM_KEY" ] ; then
if [ -n "$IPFS_SWARM_KEY" ] ; then
echo "Copying swarm key from variable..."
echo -e "$IPFS_SWARM_KEY" >"$SWARM_KEY_FILE" || exit 1
printf "%s\n" "$IPFS_SWARM_KEY" >"$SWARM_KEY_FILE" || exit 1
chmod $SWARM_KEY_PERM "$SWARM_KEY_FILE"
fi

Expand All @@ -43,14 +41,15 @@ else
# Check during initialization if a swarm key was provided and
# copy it to the ipfs directory with the right permissions
# WARNING: This will replace the swarm key if it exists
if [ ! -z "$IPFS_SWARM_KEY_FILE" ] ; then
if [ -n "$IPFS_SWARM_KEY_FILE" ] ; then
echo "Copying swarm key from file..."
install -m $SWARM_KEY_PERM "$IPFS_SWARM_KEY_FILE" "$SWARM_KEY_FILE" || exit 1
fi

# Unset the swarm key file variable
unset IPFS_SWARM_KEY_FILE

fi

find /container-init.d -maxdepth 1 -type f -iname '*.sh' -print0 | sort -z | xargs -n 1 -0 -r container_init_run

exec ipfs "$@"
14 changes: 14 additions & 0 deletions bin/container_init_run
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/bin/sh

set -e

# used by the container startup script for running initialization scripts

script="$1"
if [ -x "$script" ] ; then
printf "Executing '%s'...\n" "$script"
"$script"
else
printf "Sourcing '%s'...\n" "$script"
. "$script"
fi
29 changes: 27 additions & 2 deletions test/sharness/t0300-docker-image.sh
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ TEST_TESTS_DIR=$(dirname "$TEST_SCRIPTS_DIR")
APP_ROOT_DIR=$(dirname "$TEST_TESTS_DIR")

test_expect_success "docker image build succeeds" '
docker_build "$TEST_TESTS_DIR/../Dockerfile" "$APP_ROOT_DIR" >build-actual ||
docker_build "$TEST_TESTS_DIR/../Dockerfile" "$APP_ROOT_DIR" | tee build-actual ||
test_fsh echo "TEST_TESTS_DIR: $TEST_TESTS_DIR" ||
test_fsh echo "APP_ROOT_DIR : $APP_ROOT_DIR" ||
test_fsh cat build-actual
Expand All @@ -41,8 +41,18 @@ test_expect_success "docker image build output looks good" '
test_fsh cat build-actual
'

test_expect_success "write init scripts" '
echo "ipfs config Foo Bar" > 001.sh &&
echo "ipfs config Baz Qux" > 002.sh &&
chmod +x 002.sh
'

test_expect_success "docker image runs" '
DOC_ID=$(docker run -d -p 127.0.0.1:5001:5001 -p 127.0.0.1:8080:8080 "$IMAGE_ID")
DOC_ID=$(docker run -d \
-p 127.0.0.1:5001:5001 -p 127.0.0.1:8080:8080 \
-v "$PWD/001.sh":/container-init.d/001.sh \
-v "$PWD/002.sh":/container-init.d/002.sh \
"$IMAGE_ID")
'

test_expect_success "docker container gateway is up" '
Expand All @@ -53,6 +63,21 @@ test_expect_success "docker container API is up" '
pollEndpoint -host=/ip4/127.0.0.1/tcp/5001 -http-url http://localhost:5001/version -v -tries 30 -tout 1s
'

test_expect_success "check that init scripts were run correctly and in the correct order" "
echo -e \"Sourcing '/container-init.d/001.sh'...\nExecuting '/container-init.d/002.sh'...\" > expected &&
docker logs $DOC_ID 2>/dev/null | grep -e 001.sh -e 002.sh > actual &&
test_cmp actual expected
"

test_expect_success "check that init script configs were applied" '
echo Bar > expected &&
docker exec "$DOC_ID" ipfs config Foo > actual &&
test_cmp actual expected &&
echo Qux > expected &&
docker exec "$DOC_ID" ipfs config Baz > actual &&
test_cmp actual expected
'

test_expect_success "simple ipfs add/cat can be run in docker container" '
expected="Hello Worlds" &&
HASH=$(docker_exec "$DOC_ID" "echo $(cat expected) | ipfs add | cut -d' ' -f2") &&
Expand Down