
- Для веб-приложения выбирается наиболее популярный (или один из наиболее ходовых) веб-фреймворк.
- Приложение, которое создается, должно выполнять следующее действие: присылать сообщение «Hello, world!» при обращении по единственному маршруту — «/».
- Там, где это имеет смысл, используется многоэтапная сборка для оптимизации образа.
- В качестве базового образа всегда используется Alpine, либо образы на его основе.
- Время сборки измеряется Linux-командой time. Используется вот так:
time docker build -t my-image .После исполнения команды выводит время, затраченное на ее выполнение.
- Для запуска контейнера всегда используется команда
docker run --rm -it -p port1:port2 my-image
Ну что же, приступим!
Node.JS
В качестве базового образа используем node:alpine, в качестве сервера — Express. Многоэтапная сборка в данном случае сокращает образ всего на пару мегабайтов.
Код приложения:
const express = require('express'); const app = express(); const port = 8000; app.get('/', (req, res) => { res.send('Hello, world!'); }); app.listen(port, () => { console.log('The server listens on ' + port); });
Dockerfile:
FROM node:alpine AS builder COPY index.js /app/index.js WORKDIR /app RUN npm install express --save ENTRYPOINT [ "node", "/app/index.js" ] FROM node:alpine COPY --from=builder /app /app ENTRYPOINT [ "node", "/app/index.js" ]
Время сборки — 14.791 секунд.
Размер образа — 81 MB.
C#
В случае с C# использование многоэтапной сборки и Alpine в качестве production-образа сокращают размер финального образа примерно в 7 раз. В качестве фреймворка испоьзуется ASP.NET Core.
Код контроллера:
[Route("/")] [ApiController] public class ValuesController : ControllerBase { [HttpGet] public ActionResult<string> Get() { return "Hello, world!"; } }
Dockerfile:
FROM mcr.microsoft.com/dotnet/core/sdk:3.0 AS builder COPY . /app WORKDIR /app RUN dotnet publish -c Release ENTRYPOINT [ "dotnet", "/app/bin/Release/netcoreapp3.0/publish/cs-app.dll" ] FROM mcr.microsoft.com/dotnet/core/aspnet:3.0-alpine3.9 COPY --from=builder /app/bin/Release/netcoreapp3.0/publish/ /app/ ENTRYPOINT [ "dotnet", "/app/cs-app.dll" ]
Время сборки — 14.818 секунд.
Размер образа — 94.4 MB.
Java
Использование многоэтапной сборки и Alpine сокращают размер образа примерно в 6 раз. В качестве веб-фреймворка используется Spring Boot с пакетным менеджером Gradle.
Код контроллера:
@RestController public class HelloController { @RequestMapping("/") public String hello() { return "Hello, world!"; } }
Dockerfile:
FROM gradle:jdk8 AS builder COPY . /app WORKDIR /app RUN ./gradlew build ENTRYPOINT [ "java", "-jar", "build/libs/app-0.0.1-SNAPSHOT.jar" ] FROM openjdk:8-jdk-alpine COPY --from=builder /app/build/libs/app-0.0.1-SNAPSHOT.jar /app/application.jar ENTRYPOINT [ "java", "-jar", "/app/application.jar" ]
Время сборки — 1 минута 52.479 секунд.
Размер образа — 122 MB.
Время сборки является очень высоким из-за запуска демона Gradle и выполнения всех его тасков.
PHP
В качестве фреймворка был выбран Laravel. Конкретно в этом случае не было никаких дополнительных библиотек, только код, сгенерированный самим фреймворком, так что использование многоэтапной сборки не имело смысла. Нам достаточно изменить код файла routes/web.php:
Route::get('/', function () { return "Hello, world!"; });
Короткий Dockerfile:
FROM php:7.2.19-alpine3.9 COPY . /usr/src/app WORKDIR /usr/src/app ENTRYPOINT [ "php", "artisan", "serve", "--host", "0.0.0.0" ]
Время сборки — 15.046 секунд.
Размер образа — 116 MB.
Python
Многоэтапная сборка экономит всего пару мегабайтов. В качестве веб-фреймворка был выбран Flask. Код весьма прост:
from flask import Flask app = Flask(__name__) @app.route("/") def hello(): return "Hello World!" if __name__ == "__main__": app.run(host="0.0.0.0", port=int("5000"), debug=True)
Dockerfile:
FROM python:alpine3.7 AS builder COPY . /app WORKDIR /app RUN pip install --user -r requirements.txt FROM python:alpine3.7 COPY --from=builder /root/.local/lib/python3.7/site-packages /usr/local/lib/python3.7/site-packages COPY --from=builder /app/index.py /app/index.py ENTRYPOINT [ "python", "./app/index.py" ]
В файле requirements.txt прописана всего одна зависимость — flask.
Время сборки — 15.332 секунд.
Размер образа — 85.1 MB.
Go
Go имеет преимущество перед другими языками в плане построения веб-приложений. Ему не нужен какой-нибудь тяжеловесный фреймворк, все необходимое уже находится в стандартной библиотеке. При этом он компилируется напрямую в код той архитектуры, на которой будет запущена программа, так что нет необходимости в виртуальной машине, исполняющей байт-код. Мы можем собрать исполнимый файл и запустить его на чистом Alpine.
Код сервера:
package main import ( "log" "net/http" ) func hello(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Hello, world!")) } func main() { http.HandleFunc("/", hello) err := http.ListenAndServe(":80", nil) if err != nil { log.Fatalln("Couldn't start the server:", err) } }
Dockerfile:
FROM golang:1.12 AS builder COPY . /go/src/app RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /go/bin/app /go/src/app/ ENTRYPOINT [ "go/bin/app" ] FROM alpine:latest COPY --from=builder /go/bin/app /bin/app ENTRYPOINT [ "/bin/app" ]
Строка «CGO_ENABLED=0 GOOS=linux GOARCH=amd64» необходима, т.к. Alpine не имеет стандартного libc.
Время сборки — 12.568 секунд.
Размер образа — 12.9 MB.
Это просто фантастический результат. При использовании Alpine и многоэтапной сборки размер образа уменьшается в 60 раз. Бесспорно, Go — лучший язык для приложений, подлежащих упаковке в контейнер.
Что действительно может увеличить время сборки, так это скачивание библиотек при помощи go get. Пожалуй, лучше использовать для этого dep.
Ruby
Контейнеризация Rails-приложения — сущий кошмар. Пришлось столкнуться со следующими проблемами:
- Несовместимость версий. По умолчанию Ubuntu ставит Ruby 2.5.1 и Bundler 2.0.2. Но в контейнере для Ruby 2.5.1 был Bundler 1-ой версии. Если прописать в Dockerfile инструкцию по установке нового Bundler, то то среда Ruby все равно продолжит использовать старый. Решение нашлось на сайте, на который я смог попасть только через Tor.
- Сборка некоторых гемов требует исходники на Си. Хуже того, при сборке некоторых из них (конкретно — nokogiri) необходимо прописывать конфиги, валяющиеся где-то в этих исходниках. Решение этой проблемы мне повезло найти в одном японском блоге. Мало того, эти исходники необходимы даже на production’e.
Код контроллера:
class HomeController < ApplicationController def index render plain: 'Hello, world!' end end
Маршрут:
Rails.application.routes.draw do root to: 'home#index' end
Кроме того, в Gemfile надо прописать следующее:
gem 'tzinfo-data' gem 'execjs'
Поучившийся Dockerfile:
FROM ruby:2.5.1-alpine3.7 AS base ENV BUNDLER_VERSION 2.0.2 RUN apk add --no-cache --update \ build-base \ libxml2-dev \ libxslt-dev \ nodejs \ nodejs-npm \ sqlite-dev \ && gem install bundler FROM base AS builder COPY . /usr/src/app WORKDIR /usr/src/app RUN gem install nokogiri \ -- --use-system-libraries \ --with-xml2-config=/usr/bin/xml2-config \ --with-xslt-config=/usr/bin/xslt-config \ && bundle install CMD rails server -b 0.0.0.0 FROM base COPY --from=builder /usr/src/app /usr/src/app COPY --from=builder /usr/local/bundle/ /usr/local/bundle/ WORKDIR /usr/src/app CMD rails server -b 0.0.0.0
Есть, конечно, официальный образ Rails, но его поддержка была прекращена в 2016-ом.
Время сборки — 2 минуты 20.374 секунд.
Размер образа — 322 MB.
Это очень много. Объективно наихудший результат среди всех представленных здесь языков.
Вообще, большинство языков из присутствующих здесь появились именно в те времена, когда о контейнеризации никто и не подозревал, а кроссплатформенность достигалась с помошью виртуальных машин, исполняющих байт-код. Go пошел кардинально другим путем, но и повезо ему больше всех.
Если у вас есть какие-либо советы или замечания по оптимизации образов, пожалуйста, пишите в комментариях, все учтется.
ссылка на оригинал статьи https://habr.com/ru/post/462369/
Добавить комментарий