Skip to content

mtrpz-course/docker-practice

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

31 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

docker-practice

Navigation:

  1. Python

  2. Go

  3. Kotlin

  4. Conclusion

PYTHON


  1. Unoptimized build
 FROM ubuntu:latest
 MAINTAINER Michael Chirozidi '[email protected]'
 RUN apt-get update -qy
 RUN apt-get install -qy python3.10 python3-pip python3.10-dev
 COPY . /app
 WORKDIR /app
 RUN pip install pipreqs
 RUN pipreqs &&  echo 'uvicorn[standard]==0.21.1' >> requirements.txt
 RUN pip install -r requirements.txt
 CMD ["uvicorn", "spaceship.main:app", "--host=0.0.0.0","--port=8080"] 
  • Build: bash $ docker build . -t py-fastapi:1.0
  • Run: bash $ docker run -p 8080:8080 --rm py-fastapi:1.0
  • Build time: 29.2s
  • Build size: 484MB
  • Description: Regular unoptimized build.
  • Commit link

  1. Changing of app.py
  • Build: bash $ docker build . -t py-fastapi:2.0
  • Run: bash $ docker run -p 8080:8080 --rm py-fastapi:2.0
  • Build time: 8s
  • Build size: 484MB (same)
  • Description: Faster build time because the base image ubuntu:latest was cached.
  • Commit link

  1. Dockerfile optimization
 FROM python:slim-bullseye
 MAINTAINER Michael Chirozidi [email protected]
 COPY requirements/backend.in .
 RUN pip install --no-cache-dir -r backend.in
 COPY . .
 CMD ["uvicorn", "spaceship.main:app", "--host=0.0.0.0", "--port=8080"] 
  • Build: bash $ docker build . -t py-fastapi:3.0
  • Run: bash $ docker run -p 8080:8080 --rm py-fastapi:3.0
  • Build time: 8.7s
  • Build size: 168MB
  • Description: First, the python dependency file is copied separately from other directory contents, dependencies are installed. Docker caches the layer with dependencies separately, so if the dependencies don't change, it will be used from the cache. Second, the basic image is more compact and do have neccessary packets only.
  • Commit link

  1. Upgrading of app.py
  • Build: bash $ docker build . -t py-fastapi:4.0
  • Run: bash $ docker run -p 8080:8080 --rm py-fastapi:4.0
  • Build time: 1.4s
  • Build size: 168MB (same)
  • Description: Faster build time because the base image python:slim-bullseye was cached.
  • Commit link

  1. NumPy
 NumPy==1.24.3
 fastapi==0.95.0
 pydantic==1.10.7
 starlette==0.26.1
 uvicorn[standard]==0.21.1
 pyproject.toml==0.0.10 

On Bullseye

  • Build: bash $ docker build . -t py-fastapi:5.0
  • Run: bash $ docker run -p 8080:8080 --rm py-fastapi:5.0
  • Build time: 1.4s
  • Build size: 168MB (same)
  • Description: More slowly build because new library was added
  • Commit link

On Alpine

FROM python:alpine
MAINTAINER Michael Chirozidi [email protected]
RUN apk add --no-cache musl-dev g++ gcc lapack-dev
WORKDIR /app
COPY requirements/backend.in .
RUN pip install --no-cache-dir -r backend.in
COPY . .
CMD ["uvicorn", "spaceship.main:app", "--host=0.0.0.0", "--port=8080"]
  • Build: bash $ docker build . -t py-fastapi:5.0
  • Run: $ bash docker run -p 8080:8080 --rm py-fastapi:5.0
  • Build time: 125.5s
  • Build size: 415MB
  • Description: At the build based on alpine, not all dependencies were installed for successful numpy build (I wanted to die during this struggle), so I added an instruction in the Dockerfile that installs the necessary dependencies (g++, gcc, musl-dev, ...). The library download resulted in long connection time and a large image size.
  • Commit link

GOLANG

  1. Default build
 FROM golang:latest
 MAINTAINER Michael Chirozidi [email protected]
 WORKDIR /app
 COPY go.mod go.sum ./
 RUN go mod download
 COPY . .
 RUN go build -o build/fizzbuzz
 EXPOSE 8080
 CMD ["./build/fizzbuzz", "serve"] 
  • Build: bash $ docker build . -t golang:1.0
  • Run: bash $ docker run -p 8080:8080 --rm golang:1.0
  • Build time: 137.7s
  • Build size: 837.42MB
  • Description: Minimal Dockerfile
  • Commit link

  1. Multi-stage building with Scratch
 FROM golang:latest AS builder
 MAINTAINER Michael Chirozidi [email protected]
 WORKDIR /app
 COPY go.mod go.sum ./
 RUN go mod download
 COPY . .
 RUN CGO_ENABLED=0 go build -ldflags "-w -s -extldflags '-static'" -o build/fizzbuzz
 
 FROM scratch
 COPY --from=builder /app/build/fizzbuzz /
 COPY --from=builder /app/templates/index.html /templates/
 EXPOSE 8080
 CMD ["./build/fizzbuzz", "serve"] 
  • Build: bash $ docker build . -t golang:2.0
  • Run: bash $ docker run -p 8080:8080 --rm golang:2.0
  • Build time: 9.5s
  • Build size: 6.55MB
  • Description: First, an intermediate image is created with the Go dependencies, and the application code is compiled into a binary file. Then, a final image is created, and the HTML page from the intermediate image and the executable file are copied into it.
  • Commit link

  1. Multi-stage building with Distorless
 FROM golang:latest AS builder
 MAINTAINER Michael Chirozidi [email protected]
 WORKDIR /app
 COPY go.mod go.sum ./
 RUN go mod download
 COPY . .
 RUN go build -o build/fizzbuzz
 FROM gcr.io/distroless/base
 COPY --from=builder /app/build/fizzbuzz /
 COPY --from=builder /app/templates/index.html /templates/
 EXPOSE 8080
 CMD ["./build/fizzbuzz", "serve"] 
  • Build: bash $ docker build . -t golang:3.0
  • Run: bash $ docker run -p 8080:8080 --rm golang:3.0
  • Build time: 1.8s
  • Build size: 27MB
  • Description: In this case, we compile the executable file using the "go build" command, which uses dynamic libraries by default, unlike the previous image where it was necessary to compile a static binary file.
  • Commit link

If we compare using a Scratch image and a Distroless image as the base, Scratch allows us to create a highly optimized and lightweight image that contains only the necessary components and dependencies. However, it requires manually installing all the dependencies and libraries inside the image, which takes time and expertise, and may not be the best choice during development.

Distroless images are designed to be used with specific programming languages and frameworks such as Java, Python, or Node.js, and already include dependencies and libraries for those languages and frameworks. Therefore, if you don't want to deal with manually installing dependencies and libraries and prefer to use a ready-made image that already contains all the necessary components to run your program, a Distroless image can be the best choice.

It should be noted that using a multi-stage build, regardless of the choice of the base image, is more optimal compared to using a golang-based image for the reasons mentioned earlier.


KOTLIN

  1. Code of simple server on Ktor framework for Kotlin language
  • Application.kt:
fun main() {
    embeddedServer(Netty, port = 8080, host = "localhost", module = Application::module)
        .start(wait = true)
}

fun Application.module() {
    configureSerialization()
    configureRouting()
}
  • Serialization.kt:
fun Application.configureSerialization() {
    install(ContentNegotiation) {
        json()
    }
    helloWorldJson()
}
  • Routing.kt:
fun Application.configureRouting() {
    helloWorldRouting()
}
  • HelloWorldRemote.kt:
fun Application.helloWorldRouting() {
    routing {
        get("/") {
            call.respondText("Hello World!", ContentType.Text.Plain, HttpStatusCode.OK)
        }
    }
}

fun Application.helloWorldJson(){
    routing {
        get("/json/hello") {
            call.respond(HttpStatusCode.OK, mapOf("hello" to "world"))
        }
    }
}
  1. Dockerfile for mutli-stage building image
FROM gradle:7-jdk11 AS build
COPY --chown=gradle:gradle . /app
WORKDIR /app
RUN gradle buildFatJar --no-daemon

FROM openjdk:11
EXPOSE 8080:8080
RUN mkdir /server
COPY --from=build /app/build/libs/*.jar /server/kotlin-all.jar
ENTRYPOINT ["java","-jar","/server/kotlin-all.jar"]
  • Build: bash $ docker build . -t kotlin:1.0
  • Run: bash $ docker run -p 8080:8080 --rm kotlin:1.0
  • Build time: 682.8s
  • Build size: 660.5MB
  • Description: Such a long build of the image is due to the fact that the build-system Gradle, after installing all dependencies, generates a special FAT JAR file for the successful operation of the server in runtime.
  • Commit link

CONCLUSION

After familiarizing myself with working with Docker, the following conclusions can be drawn:

  • Docker is a powerful tool for managing containerized applications. It allows you to create, deploy, and manage application containers that provide an isolated environment for software execution.
  • Using Docker allows you to ensure the portability and consistency of the environment. You can create an image with your application and all dependencies, and then run it on any environment where Docker is installed. This avoids problems associated with configuration differences between different systems.
  • Docker allows you to deploy applications quickly and efficiently. You can easily install and run containers with applications, which allows you to speed up software development, testing, and deployment.
  • Docker allows you to create multi-stage builds, which makes it easier to optimize and reduce the size of images. You can divide the build process into several stages to minimize the number of dependencies and include only the necessary components in the final image.
  • The Docker community is active and growing. There are a large number of publicly available images and tools to help you work with Docker. You can find many resources, documentation, and examples online to help you learn and use Docker effectively.

In general, working with Docker allows you to simplify the deployment and management of applications, provides portability and consistency of the environment, and allows you to speed up the development process. The choice between different types of images, such as Scratch and Distroless, depends on your needs and project requirements. With Docker, you can create an infrastructure that is easily scalable and maintainable, which contributes to the efficient operation of the software.