Я работаю над контейнеризацией существующего приложения, построенного на Grails. Мне удалось успешно собрать проект в образе debian:stretch
(да, я знаю, что он старый, но проект изначально был собран со старыми версиями почти всего) с установленными gradle
, maven
и default-jdk
. Однако я не уверен, означает ли это, что мне не нужен Грааль. Когда я пытаюсь выполнить grails --version
из командной строки в контейнере, он говорит: «bash: grails: команда не найдена».
Кажется, нет отдельного пакета для grails
, который я могу установить с помощью apt
, и все ссылки, кажется, говорят об установке Grails через sdkman
.
Я совсем не знаком с Grails или Groovy, поэтому не знаю, как действовать дальше. Любые советы о том, как установить (или, может быть, проверить установку?) Grails? Если это необходимо, как сборка удалась без него?
Это то, что мы используем в наших средах:
докер-compose.yml
version: '3.7'
services:
db:
image: postgres:12.3
environment:
- POSTGRES_DB=databasename
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
ports:
- 5432:5432
networks:
- mynetwork
backend:
build:
context: .
target: development
ports:
- 8080:8080
- 8083:8443
- 5006:5005
environment:
- DB_HOST=db
- DB_NAME=databasename
- DB_PORT=5432
- DB_USERNAME=postgres
- DB_PASSWORD=postgres
volumes:
- grails-volume:/root/.m2
- grails-volume:/root/.gradle
- grails-volume:/root/.grails
- ./grails-app:/app/grails-app
- ./src:/app/src
- ./version.txt:/version.txt
entrypoint: ["sh", "-c", "./wait-for-it.sh db:5432 -t 30 -- grails run-app"]
command: [""]
depends_on:
- db
networks:
- mynetwork
volumes:
grails-volume:
networks:
mynetwork:
Докерфайл:
# Image to start project and initialize dependencies.
FROM openjdk:8 AS initializer
ENV GRAILS_VERSION 4.0.3
# Install Grails
WORKDIR /usr/lib/jvm
RUN ls -l
RUN wget https://github.com/grails/grails-core/releases/download/v$GRAILS_VERSION/grails-$GRAILS_VERSION.zip && \
unzip grails-$GRAILS_VERSION.zip && \
rm -rf grails-$GRAILS_VERSION.zip && \
ln -s grails-$GRAILS_VERSION grails
# Setup Grails path.
ENV GRAILS_HOME /usr/lib/jvm/grails
ENV PATH $GRAILS_HOME/bin:$PATH
ENV GRADLE_USER_HOME /app/.gradle
# Create minimal structure to trigger grails build with specified profile.
RUN mkdir /app \
&& mkdir /app/grails-app \
&& mkdir /app/grails-app/conf \
&& echo "grails.profile: rest-api" > /app/grails-app/conf/application.yml
# Set Workdir
WORKDIR /app
# Copy minimun files to trigger grails download of wrapper and dependencies.
COPY gradle.properties build.gradle /app/
# Trigger gradle build
RUN [ "grails", "stats" ]
# Implemented to improve cache in CI
FROM initializer as development
# Add wait-for-it ro wait for database
COPY wait-for-it.sh .
RUN ["chmod", "+x", "./wait-for-it.sh"]
# Copy source code
COPY grails-app /app/grails-app
COPY src /app/src
# Set Default Behavior
ENTRYPOINT ["./wait-for-it.sh", "db:5432", "-t", "30", "--", "grails", "run-app", "--debug-jvm"]
CMD [ "" ]
# Image used to build prod war
FROM development AS builder
# Build project
RUN [ "grails", "prod", "war" ]
RUN ls -l /app/build/libs
# Production image
FROM openjdk:8-jdk AS production
# Set correct timezone
ENV TZ=America/Argentina/Cordoba
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# Add wait-for-it ro wait for database
COPY wait-for-it.sh .
RUN ["chmod", "+x", "/wait-for-it.sh"]
# Copy war inside container
COPY --from=builder /app/build/libs/app-*.war app.war
# Expose default port
EXPOSE 8080
# Wait for database to be available
ENTRYPOINT ["/wait-for-it.sh", "db-service:5432", "-t", "30", "--"]
# War runs directly. (Uses urandom as entropy source for faster startup time)
CMD ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.war"]
ждать-это-это.sh:
#!/usr/bin/env bash
# Use this script to test if a given TCP host/port are available
WAITFORIT_cmdname=${0##*/}
echoerr() { if [[ $WAITFORIT_QUIET -ne 1 ]]; then echo "$@" 1>&2; fi }
usage()
{
cat << USAGE >&2
Usage:
$WAITFORIT_cmdname host:port [-s] [-t timeout] [-- command args]
-h HOST | --host=HOST Host or IP under test
-p PORT | --port=PORT TCP port under test
Alternatively, you specify the host and port as host:port
-s | --strict Only execute subcommand if the test succeeds
-q | --quiet Don't output any status messages
-t TIMEOUT | --timeout=TIMEOUT
Timeout in seconds, zero for no timeout
-- COMMAND ARGS Execute command with args after the test finishes
USAGE
exit 1
}
wait_for()
{
if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then
echoerr "$WAITFORIT_cmdname: waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT"
else
echoerr "$WAITFORIT_cmdname: waiting for $WAITFORIT_HOST:$WAITFORIT_PORT without a timeout"
fi
WAITFORIT_start_ts=$(date +%s)
while :
do
if [[ $WAITFORIT_ISBUSY -eq 1 ]]; then
nc -z $WAITFORIT_HOST $WAITFORIT_PORT
WAITFORIT_result=$?
else
(echo > /dev/tcp/$WAITFORIT_HOST/$WAITFORIT_PORT) >/dev/null 2>&1
WAITFORIT_result=$?
fi
if [[ $WAITFORIT_result -eq 0 ]]; then
WAITFORIT_end_ts=$(date +%s)
echoerr "$WAITFORIT_cmdname: $WAITFORIT_HOST:$WAITFORIT_PORT is available after $((WAITFORIT_end_ts - WAITFORIT_start_ts)) seconds"
break
fi
sleep 1
done
return $WAITFORIT_result
}
wait_for_wrapper()
{
# In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692
if [[ $WAITFORIT_QUIET -eq 1 ]]; then
timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --quiet --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT &
else
timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT &
fi
WAITFORIT_PID=$!
trap "kill -INT -$WAITFORIT_PID" INT
wait $WAITFORIT_PID
WAITFORIT_RESULT=$?
if [[ $WAITFORIT_RESULT -ne 0 ]]; then
echoerr "$WAITFORIT_cmdname: timeout occurred after waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT"
fi
return $WAITFORIT_RESULT
}
# process arguments
while [[ $# -gt 0 ]]
do
case "$1" in
*:* )
WAITFORIT_hostport=(${1//:/ })
WAITFORIT_HOST=${WAITFORIT_hostport[0]}
WAITFORIT_PORT=${WAITFORIT_hostport[1]}
shift 1
;;
--child)
WAITFORIT_CHILD=1
shift 1
;;
-q | --quiet)
WAITFORIT_QUIET=1
shift 1
;;
-s | --strict)
WAITFORIT_STRICT=1
shift 1
;;
-h)
WAITFORIT_HOST = "$2"
if [[ $WAITFORIT_HOST == "" ]]; then break; fi
shift 2
;;
--host=*)
WAITFORIT_HOST = "${1#*=}"
shift 1
;;
-p)
WAITFORIT_PORT = "$2"
if [[ $WAITFORIT_PORT == "" ]]; then break; fi
shift 2
;;
--port=*)
WAITFORIT_PORT = "${1#*=}"
shift 1
;;
-t)
WAITFORIT_TIMEOUT = "$2"
if [[ $WAITFORIT_TIMEOUT == "" ]]; then break; fi
shift 2
;;
--timeout=*)
WAITFORIT_TIMEOUT = "${1#*=}"
shift 1
;;
--)
shift
WAITFORIT_CLI=("$@")
break
;;
--help)
usage
;;
*)
echoerr "Unknown argument: $1"
usage
;;
esac
done
if [[ "$WAITFORIT_HOST" == "" || "$WAITFORIT_PORT" == "" ]]; then
echoerr "Error: you need to provide a host and port to test."
usage
fi
WAITFORIT_TIMEOUT=${WAITFORIT_TIMEOUT:-15}
WAITFORIT_STRICT=${WAITFORIT_STRICT:-0}
WAITFORIT_CHILD=${WAITFORIT_CHILD:-0}
WAITFORIT_QUIET=${WAITFORIT_QUIET:-0}
# check to see if timeout is from busybox?
WAITFORIT_TIMEOUT_PATH=$(type -p timeout)
WAITFORIT_TIMEOUT_PATH=$(realpath $WAITFORIT_TIMEOUT_PATH 2>/dev/null || readlink -f $WAITFORIT_TIMEOUT_PATH)
if [[ $WAITFORIT_TIMEOUT_PATH =~ "busybox" ]]; then
WAITFORIT_ISBUSY=1
WAITFORIT_BUSYTIMEFLAG = "-t"
else
WAITFORIT_ISBUSY=0
WAITFORIT_BUSYTIMEFLAG = ""
fi
if [[ $WAITFORIT_CHILD -gt 0 ]]; then
wait_for
WAITFORIT_RESULT=$?
exit $WAITFORIT_RESULT
else
if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then
wait_for_wrapper
WAITFORIT_RESULT=$?
else
wait_for
WAITFORIT_RESULT=$?
fi
fi
if [[ $WAITFORIT_CLI != "" ]]; then
if [[ $WAITFORIT_RESULT -ne 0 && $WAITFORIT_STRICT -eq 1 ]]; then
echoerr "$WAITFORIT_cmdname: strict mode, refusing to execute subprocess"
exit $WAITFORIT_RESULT
fi
exec "${WAITFORIT_CLI[@]}"
else
exit $WAITFORIT_RESULT
fi
приложение.yml
#...Omitted code...
environments:
development:
dataSource:
driverClassName: org.postgresql.Driver
dialect: org.hibernate.dialect.PostgreSQLDialect
# dbCreate: create-drop
dbCreate: update
url: jdbc:postgresql://${DB_HOST}:${DB_PORT}/${DB_NAME}
test:
dataSource:
dbCreate: update
url: jdbc:h2:mem:testDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
production:
dataSource:
driverClassName: org.postgresql.Driver
dialect: org.hibernate.dialect.PostgreSQLDialect
dbCreate: update
url: jdbc:postgresql://${DB_HOST}:${DB_PORT}/${DB_NAME}
В процессе разработки вы должны использовать файл docker-compose для запуска образа (обратите внимание, что он имеет несколько томов, чтобы избежать необходимости перезапускать приложение при его изменении): docker-compose up -d backend
Чтобы создать изображение для производства: docker build -t yourdockerhubuser/yourproject:versionnumber .
Файл wait-for-it.sh
не нужен для создания образа Docker. Но нам это пригодится, чтобы заставить службу grails ждать несколько секунд, пока служба базы данных не будет готова принимать соединения...
Если вы посмотрите на Dockerfile, вы заметите, что это многоэтапный файл. Это означает, что на нем есть несколько этапов. На первом есть инструменты Grails. Последний используется в производстве и имеет только jdk8 (и, вероятно, jre будет достаточно). Таким образом, вы не загрязняете производственное изображение, что приводит к уменьшению изображения.
Очень полезно. Многоэтапная сборка — это именно то, что я пытаюсь сделать, и я могу подтвердить, что для этого проекта (в котором используются старые версии всего)
tomcat:10-jdk8-openjdk-slim
не слишком велик (312 МБ) и, похоже, имеет все необходимое для запуска файла.war
.