Контейнерный степпинг Spring Boot и решения (1)

контейнер
С тех пор, как я начал играть в Kubernetes и Spring Boot в 2017 году, я уже более 2 лет иду по этой дороге без возврата, наступая на череду небольших ям посередине. Вот унифицированный обзор конкретных решений.
Ожидается, что он будет разделен примерно на главы 4. В этом выпуске в основном обобщаются некоторые вопросы, связанные с конфигурацией, журналами и зеркалированием. Следующая часть в основном посвящена непрерывной интеграции, а затем мониторингу. Наконец, речь идет о кластерах.

Профиль Spring и переменные среды

Мы знаем, что в DevOps на основе Docker мы должны убедиться, что как можно больше сред имеют один образ. Для того, чтобы обеспечить единообразие кода в каждой среде. В соответствии с нашей реальной ситуацией мы использовали не решение центра конфигурации, а решение переменной среды.

Spring Boot по умолчанию поддерживает конфигурацию с несколькими средами. Мы можем выполнить различие в конфигурации различных сред или разных кластеров с помощью Spring Profile.

В частности, переменная среды SPRING_PROFILES_ACTIVE может использоваться для указания используемой конфигурации среды. Конкретные команды следующие:


docker run -d -p 8080:8080 -e “SPRING_PROFILES_ACTIVE=dev” –name test testImage:latest


Обычно мы используем внутреннюю конфигурацию управления несколькими файлами, а среда разделена на пять. Это локальная среда, разработка, тестирование, предварительная и профессиональная версии, которые соответствуют локальной среде отладки, среде разработки, среде тестирования, предварительной версии и формальной среде. Всего создается 5 файлов конфигурации, а именно: applicaiton.yaml, applicaiton-local.yaml, applicaiton-dev.yaml, applicaiton-test.yaml, applicaiton-pre.yaml, applicaiton-prod.yaml. В applicaiton.yaml кладем общедоступную конфигурацию, например конфигурацию jackson, какую-то kafka, конфигурацию mybatis. Для MySQL конфигурация подключения Kafka и т. д. хранятся в конфигурации среды. По умолчанию среда выбирает локальную. При развертывании в каждой среде переключение конфигурации выполняется путем перезаписи переменных среды.

После принятия этого метода мы также столкнулись с другой проблемой, например, адрес онлайн-подключения к MySQL будет напрямую доступен людям с правами доступа к коду, что очень опасно, поэтому для этих конфигураций мы также используем внедрение переменных среды по умолчанию. Информация о конфигурации формальной среды, как правило, может знать только эксплуатация и техническое обслуживание, когда конфигурация эксплуатации и технического обслуживания позволяет им вводить.

Например:

spring.redis.host=${REDIS_HOST}
spring.redis.port=${REDIS_PORT}
spring.redis.timeout=30000

docker run -d -p 8080:8080 -e "SPRING_PROFILES_ACTIVE=dev" -e "REDIS_HOST=127.0.0.1" -e "REDIS_PORT=3306" --name test testImage:latest


В нашем коде есть некоторые другие ситуации, нам нужно судить, нужно ли нам настраивать bean-компоненты в соответствии с переменными среды. Например, чванство, которое мы не хотим запускать в производственной среде. В этом случае мы используем @Profile, чтобы определить, нужно ли инициализировать bean-компонент.
Например:

@Component
@Profile("dev")
public class DatasourceConfigForDev


@Configuration
@EnableSwagger2
@Profile( "dev")
public class SwaggerConfig {
}

Журналы после контейнеризации Spring Boot

На практике мы используем Kubernetes для планирования контейнеров и ES для хранения журналов. В настоящее время в коллекции журналов приложений всего 4 стандартных решения.

Журналы приложений первого типа передаются напрямую по сети в компонент сбора логов, а затем в ES. Например, LogstashSocketAppender logstash-logback-encoder, если объем журнала слишком велик, он может быть сначала введен в канал сообщений, а затем собран сборщиком журнала. Этот метод увеличит ресурсы ЦП и памяти, занимаемые приложением, а также требует относительно стабильной сетевой среды.

Второй способ — вывести журнал в фиксированный каталог и смонтировать каталог в локальное или сетевое хранилище для обработки сборщиком журналов. Таким образом, в логах будет отсутствовать информация о подах о Kubernetes. необходимо компенсировать другими способами.

Третий способ — выводить лог напрямую в консоль, затем передавать его в Docker для записи лога, а затем собирать его через сборщик логов. Поскольку на хосте запущены различные типы контейнеров, если не производится специальная обработка, стоимость разбора логов будет очень и очень высока.

В четвертом способе на каждое приложение вешается отдельный вспомогательный контейнер для полного разбора и сбора логов. Это потребует некоторых ресурсов. Пока инструмент сбора журналов во вспомогательном контейнере выбран правильно, это действительно лучшее решение.

Основываясь на приведенном выше централизованном решении, мы выбрали третье в соответствии с нашей собственной ситуацией.Чтобы избежать различных работ по разбору журнала в процессе сбора, мы надеемся, что вывод журнала должен быть в формате Json, насколько это возможно. Здесь мы используем logstash-logback-encoder для ее решения, который выводит JSON с фиксированной структурой. В сочетании с описанной выше конфигурацией синтаксического анализа нескольких сред мы создали logback-kubernetes.xml.Для среды, которая должна работать в контейнере, logback-kubernetes.xml указывается конфигурацией в качестве файла конфигурации журнала. Таким образом, при локальной разработке мы можем с радостью использовать журнал Spring Boot по умолчанию.

Вопросы о запуске Java в контейнере

В настоящее время мы используем Java 8, и JDK выбрал openJDK. Что касается того, почему мы выбрали openJDK, основная причина в том, что в самом начале мы еще не запаковали внутренний образ, следуя туториалу, мы вошли в лагерь openJDK (оракул еще не начал публиковать образ oracle jdk на docker hub в то время). Теперь давайте посмотрим, что я должен немного развлечься, похоже, что в будущем я смогу использовать только openJDK. Ведь с новым режимом авторизации Java 11 нам еще нужно подумать, стоит ли его использовать.

До Java 8u131, поскольку JVM не могла распознать, что она работает в контейнере, не было возможности автоматически распределять параметры во время выполнения в соответствии с ЦП и памятью, ограниченными контейнером, что часто приводило к проблеме уничтожения OOM (мы также пробовал ручное выделение, куча памяти.Она относительно ограничена, но не куча области не сильно ограничена.Для некоторых java-приложений требуется повторная отладка.Нет возможности обобщать и автоматически расширять и сжимать). Позже мы нашли https://github.com/fabric8io-images/java/tree/master/images/jboss/openjdk8/jdk, этот образ может автоматически обращаться к cgroup для получения информации о процессоре и памяти и вычислять относительно разумную конфигурацию jvm. параметры. На основе этой идеи мы также создали свой внутренний соответствующий скрипт (система мониторинга другая), но процесс настройки не очень прозрачен.

После JRE 8u131 в JVM добавлено -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap, которое можно использовать для определения лимита памяти в контейнере (принцип можно найти в Baidu, который здесь обсуждаться не будет). Учитывая, что в нормальных условиях наш ЦП не будет заполнен, а основным узким местом станет память, поэтому мы инкапсулировали новый образ. Зеркало примерно такое:


FROM alpine:3.8

ENV LANG="en_US.UTF-8" \
    LANGUAGE="en_US.UTF-8" \
    LC_ALL="en_US.UTF-8" \
    TZ="Asia/Shanghai"

RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/' /etc/apk/repositories \
    && apk add --no-cache tzdata curl ca-certificates \
    && echo "${TZ}" > /etc/TZ \
    && ln -sf /usr/share/zoneinfo/${TZ} /etc/localtime \
    && rm -rf /tmp/* /var/cache/apk/*

ENV JAVA_VERSION_MAJOR=8 \
    JAVA_VERSION_MINOR=181 \
    JAVA_VERSION_BUILD=13 \
    JAVA_VERSION_BUILD_STEP=r0 \
    JAVA_PACKAGE=openjdk \
    JAVA_JCE=unlimited \
    JAVA_HOME=/usr/lib/jvm/default-jvm \
    DEFAULT_JVM_OPTS="-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=2 -XX:+UseG1GC"

RUN apk add --no-cache openjdk8-jre=${JAVA_VERSION_MAJOR}.${JAVA_VERSION_MINOR}.${JAVA_VERSION_BUILD}-${JAVA_VERSION_BUILD_STEP} \
    && echo "securerandom.source=file:/dev/urandom" >> /usr/lib/jvm/default-jvm/jre/lib/security/java.security \
    && rm -rf /tmp/*  /var/cache/apk/*


На данный момент, даже если наш базовый образ Java был упакован, он относительно хорошо решил некоторые проблемы, связанные с запуском Java в контейнере. Что касается проблемы будущего апгрейда, то Java 8u191 и Java 11 уже решили проблему ограниченности ресурсов, и у меня есть время поговорить об этом отдельно (и вырыть себе яму),Так что не надо об этом думать, если не боитесь смерти, помогите попробовать Java 11.

Для получения конкретной информации об образе см. https://github.com/XdaTk/DockerImages.


О Spring Boot и Tomcat APR

Для контейнера Spring Boot мы используем Tomcat.После того, как вы попробуете откат в течение определенного периода времени, он действительно будет потреблять меньше памяти. Однако, поскольку мониторинг еще не идеален, наша главная сила на данный момент — это Tomcat. Если кто-то обновится до Spring Boot 2.0, он может заметить журнал WARN о Tomcat APR при запуске. Что касается APR, вы можете обратиться к http://tomcat.apache.org/tomcat-9.0-doc/apr.html.

Для производительности мы решили переключиться в режим APR. На основе образа Java, упомянутого выше, мы снова продолжаем инкапсулировать его.

FROM xdatk/openjdk:8.181.13-r0 as native

ENV TOMCAT_VERSION="9.0.13" \
    APR_VERSION="1.6.3-r1" \
    OPEN_SSL_VERSION="1.0.2p-r0"
ENV TOMCAT_BIN="https://mirrors.tuna.tsinghua.edu.cn/apache/tomcat/tomcat-9/v${TOMCAT_VERSION}/bin/apache-tomcat-${TOMCAT_VERSION}.tar.gz"

RUN apk add --no-cache apr-dev=${APR_VERSION} openssl-dev=${OPEN_SSL_VERSION} openjdk8=${JAVA_VERSION_MAJOR}.${JAVA_VERSION_MINOR}.${JAVA_VERSION_BUILD}-${JAVA_VERSION_BUILD_STEP} wget unzip make g++ \
    && cd /tmp \
    && wget -O tomcat.tar.gz ${TOMCAT_BIN} \
    && tar -xvf tomcat.tar.gz \
    && cd apache-tomcat-*/bin \
    && tar -xvf tomcat-native.tar.gz \
    && cd tomcat-native-*/native \
    && ./configure --with-java-home=${JAVA_HOME} \
    && make \
    && make install


FROM xdatk/openjdk:8.181.13-r0
ENV TOMCAT_VERSION="9.0.13" \
    APR_VERSION="1.6.3-r1" \
    OPEN_SSL_VERSION="1.0.2p-r0" \
    APR_LIB=/usr/local/apr/lib

COPY --from=native ${APR_LIB} ${APR_LIB}

RUN apk add --no-cache apr=${APR_VERSION} openssl=${OPEN_SSL_VERSION}

После тестирования будут некоторые советы по производительности.

Выше мы в основном гарантируем, что Spring Boot может нормально работать в контейнере. Далее нам нужно передать код в производство, так что следите за следующей главой.