Skip to content

Commit

Permalink
feat: in docker debug support with delve (#1789)
Browse files Browse the repository at this point in the history
  • Loading branch information
dadrus committed Sep 30, 2021
1 parent aac05d1 commit 37325a1
Show file tree
Hide file tree
Showing 5 changed files with 253 additions and 1 deletion.
18 changes: 18 additions & 0 deletions .docker/Dockerfile-debug
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
FROM golang:1.16-buster
ENV CGO_ENABLED 1

RUN apt-get update && apt-get install -y --no-install-recommends inotify-tools psmisc
RUN go get github.com/go-delve/delve/cmd/dlv

COPY script/debug-entrypoint.sh /entrypoint.sh

VOLUME /dockerdev

WORKDIR /dockerdev

ENV DELVE_PORT 40000
ENV SERVICE_NAME service

EXPOSE 8000 $DELVE_PORT

ENTRYPOINT ["/entrypoint.sh"]
17 changes: 17 additions & 0 deletions .docker/docker-compose.template.dbg
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
version: '3.7'

services:
${SERVICE_NAME}:
build:
dockerfile: ./.docker/Dockerfile-debug
ports:
- ${REMOTE_DEBUGGING_PORT}:40000
security_opt:
- apparmor=unconfined
cap_add:
- SYS_PTRACE
volumes:
- type: bind
source: ${SERVICE_ROOT}
target: /dockerdev
read_only: false
141 changes: 141 additions & 0 deletions docs/docs/debug/debugging_in_docker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
---
id: debug-docker-delve-ory-kratos
title: Debugging Ory Kratos in Docker with Delve
---

Very often, there is a need to debug Kratos being deployed as a Docker image. To
support this, Kratos ships with a couple of files:

- The `Dockerfile-debug` file, which you can find in the `.docker` directory.
- The `docker-compose.template.dbg` file, which you can find in the same
directory. This file defines a template for a service, one would like to debug
in Docker
- and a supplementary `debug-entrypoint.sh` skript, located in the `script`
directory.

Actually, these files do not include any Kratos specifica and thus can be used
for any Golang based project. As you already could infer, this support is meant
to be used in a docker-compose setup as described below. You can however run it
as a standalone Docker container as well. You can find some information on how
to achieve this at the end of this document.

## As part of a docker-compose setup

Imagine you have the following project structure:

- docker-compose - a directory containing your `docker-compose.yaml` file
- kratos - a directory containing the Kratos code
- kratos-frontend - a directory containing a frontend application for Kratos

The `docker-compose.yml` mentioned above could look as follows:

```yaml
version: '3.7'

volumes:
postgres-db:

services:
postgresd:
image: postgres:9.6
ports:
- '5432:5432'
volumes:
- type: volume
source: postgres-db
target: /var/lib/postgresql/data
read_only: false
environment:
- PGDATA=/var/lib/postgresql/data/pgdata
- POSTGRES_PASSWORD=secret
- POSTGRES_USER=kratos

kratos-migrate:
image: kratos
build:
context: ../kratos
dockerfile: ./.docker/Dockerfile-build
environment:
- DSN=postgres://kratos:secret@postgresd:5432/kratos?sslmode=disable&max_conns=20&max_idle_conns=4
volumes:
- type: bind
source: path-to-kratos-config
target: /etc/config/kratos
command: migrate sql -e --yes
depends_on:
- postgresd

kratos:
image: kratos
build:
context: ../kratos
dockerfile: ./.docker/Dockerfile-build
depends_on:
- kratos-migrate
ports:
- '4433:4433' # public
- '4434:4434' # admin
command: serve -c /etc/config/kratos/kratos.yml --watch-courier --dev
volumes:
- type: bind
source: path-to-kratos-config
target: /etc/config/kratos

kratos-frontend:
image: kratos-frontend
build:
context: ../kratos-kratos-frontend
dockerfile: ./Dockerfile
env_file:
- file-containing-all-required-configuration.env
```

To enable debugging of Kratos without changing the above docker-compose file,
you can do the following (from the docker-compose directory):

```bash
SERVICE_NAME=kratos SERVICE_ROOT=../kratos REMOTE_DEBUGGING_PORT=9999 envsubst < ../kratos/.docker/docker-compose.template.dbg \
> docker-compose.kratos.tmp
docker-compose -f docker-compose.yaml -f docker-compose.kratos.tmp up --build -d kratos
```

The first line will create an overwrite docker-compose file to have a debug
configuration for the kratos service. The second line will start a debug
container by

- mounting your `kratos` directory into the resulting Docker container,
- downloading Delve,
- building Kratos inside the container,
- starting it in Delve with the arguments, you've defined in your regular
docker-compose file - in the example above, this would be
`serve -c /etc/config/kratos/kratos.yml --watch-courier --dev` - and
- watching for changes on any go file within the mounted code base.

Each time you change a .go file, the Delve process will be stopped, Kratos will
be recompiled and Delve will be started again. With other words, you'll have to
re-connect with your debugger again after each change.

As you can see from the above usage, the `docker-compose.template.dbg` template
expects the following variables to be defined:

- `SERVICE_ROOT` - the root directory of the service to be started in the debug
mode.
- `SERVICE_NAME` - the name of the service from the docker-compose file.
- `REMOTE_DEBUGGING_PORT` - the host port, the Delve listening port should be
exposed as. This is the port you should connect your remote debugger to.

If you run docker-compose this way, the container run with debugging enabled
will wait until the debugger connects. If your IDE supports remote debugging,
set host to `localhost` and port to the value, you've used for
`REMOTE_DEBUGGING_PORT` in your remote debugging configuration.

## As a standalone Docker container

If you just would like to start Kratos in a container in debug mode, you can
just use the `Dockerfile-debug` file instead of the regular `Dockerfile`. Make
however sure your build context in the root directory of Kratos and not the
`.docker` directory. In your IDE the debug configuration has to reference that
file. In addition, you'll have to expose the Delve service port 40000 under the
port 8000, as well as the actual port of the service, you'll like to access from
your host, configure the bind mounts and set the run options to
`--security-opt="apparmor=unconfined" --cap-add=SYS_PTRACE`.
3 changes: 2 additions & 1 deletion docs/sidebar.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,8 @@
{
"Debug & Help": [
"debug/csrf",
"debug/performance-out-of-memory-password-hashing-argon2"
"debug/performance-out-of-memory-password-hashing-argon2",
"debug/debugging_in_docker"
]
},
{
Expand Down
75 changes: 75 additions & 0 deletions script/debug-entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#!/bin/bash

FILE_CHANGE_LOG_FILE=/tmp/changes.log
SERVICE_ARGS="$@"

log() {
echo "***** $1 *****"
}

init() {
log "Initializing"
truncate -s 0 ${FILE_CHANGE_LOG_FILE}
tail -f ${FILE_CHANGE_LOG_FILE} &
}

build() {
log "Building ${SERVICE_NAME} binary"
go env -w GOPROXY="proxy.golang.org,direct"
go mod download
go build -gcflags "all=-N -l" -o /${SERVICE_NAME}
}

start() {
log "Starting Delve"
dlv --listen=:${DELVE_PORT} --headless=true --api-version=2 --accept-multiclient exec /${SERVICE_NAME} -- ${SERVICE_ARGS} &
}

restart() {
build

log "Killing old processes"
killall dlv
killall ${SERVICE_NAME}

start
}

watch() {
log "Watching for changes"
inotifywait -e "MODIFY,DELETE,MOVED_TO,MOVED_FROM" -m -r ${PWD} | (
while true; do
read path action file
ext=${file: -3}
if [[ "$ext" == ".go" ]]; then
echo "$file"
fi
done
) | (
WAITING=""
while true; do
file=""
read -t 1 file
if test -z "$file"; then
if test ! -z "$WAITING"; then
echo "CHANGED"
WAITING=""
fi
else
log "File ${file} changed" >> ${FILE_CHANGE_LOG_FILE}
WAITING=1
fi
done
) | (
while true; do
read TMP
restart
done
)
}

# main part
init
build
start
watch

0 comments on commit 37325a1

Please sign in to comment.