What is s2i
?
source-to-image
, or s2i
as commonly abbreviated, is a build tool that allows an app developer and/or operator the ability to take the app source code and construct an OCI image (aka docker image, aka container image).
Concepts
Builder
A builder is an image that knows how to build a specific type of source code. Although it would be possible for a single builder to support multiple languages it is common that a builder only builds one language family.
The following publically available builders are available:
Build image
The base image in which the build process is executed.
Run image
The base image in which the built application runs.
Features
Separation of concerns
By using a tool such as s2i
, the app developer can focus on adding features and maintaining the application. On the other side, DevOps can ensure that the application is built in a secure and reproducible manner by providing builders that should be used by developers.
Minimal to no configuration
In most cases, no additional s2i
configuration is necessary and it's able to build the application. In the cases where a more complex application needs to be built there are various configuration points (to be covered in a separate tutorial).
Process
The process, as well defined in the project README.md:
The
s2i build
workflow is:
s2i
creates a container based on the build image and passes it a tar file that contains:
- The application source in src, excluding any files selected by
.s2iignore
- The build artifacts in
artifacts
(if applicable - see incremental builds)s2i
sets the environment variables from.s2i/environment
(optional)s2i
starts the container and runs its assemble scripts2i
waits for the container to finishs2i
commits the container, setting the CMD for the output image to be the run script and tagging the image with the name provided.
Install
Now that we've got some idea on what s2i
is and why we might want to use it let's start by installing it.
You may find the latest releases here.
These are the command I ran to install it in /usr/local/bin/
on a macOS:
curl -sSL https://github.com/openshift/source-to-image/releases/download/v1.3.0/source-to-image-v1.3.0-eed2850f-darwin-amd64.tar.gz -o /tmp/s2i.tgz
tar -xvf /tmp/s2i.tgz -C /usr/local/bin/
s2i version
Build
Next, we'll build the app.
Let's use Spring's sample project, petclinic, as the source.
s2i build https://github.com/spring-projects/spring-petclinic fabric8/s2i-java pet-clinic
Breakdown
If we breakdown the command, this is what it all means:
s2i
build # command
https://github.com/spring-projects/spring-petclinic # source (in)
fabric8/s2i-java # builder image (in)
pet-clinic # image name (out)
Output
The [truncated] output is as follows. Nothing spectacular to look at but we do see that it built our image.
==================================================================
Starting S2I Java Build .....
S2I source build for Maven detected
Using MAVEN_OPTS '-XX:+UseParallelGC -XX:GCTimeRatio=4 -XX:AdaptiveSizePolicyWeight=90 -XX:MinHeapFreeRatio=20 -XX:MaxHeapFreeRatio=40 -XX:+ExitOnOutOfMemoryError'
Found pom.xml ...
Running 'mvn -Dmaven.repo.local=/tmp/artifacts/m2 package -DskipTests -Dmaven.javadoc.skip=true -Dmaven.site.skip=true -Dmaven.source.skip=true -Djacoco.skip=true -Dcheckstyle.skip=true -Dfindbugs.skip=true -Dpmd.skip=true -Dfabric8.skip=true -e -B '
Apache Maven 3.5.4 (1edded0938998edf8bf061f1ceb3cfdeccf443fe; 2018-06-17T18:33:14Z)
Maven home: /opt/maven
Java version: 1.8.0_252, vendor: Oracle Corporation, runtime: /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.252.b09-2.el7_8.x86_64/jre
Default locale: en_US, platform encoding: ANSI_X3.4-1968
OS name: "linux", version: "4.19.76-linuxkit", arch: "amd64", family: "unix"
[INFO] Error stacktraces are turned on.
[INFO] Scanning for projects...
...
(Downloading a bunch of dependencies)
...
[INFO] Building jar: /tmp/src/target/spring-petclinic-2.3.1.BUILD-SNAPSHOT.jar
[INFO]
[INFO] --- spring-boot-maven-plugin:2.3.1.RELEASE:repackage (repackage) @ spring-petclinic ---
[INFO] Replacing main artifact with repackaged archive
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 06:43 min
[INFO] Finished at: 2020-07-19T16:00:45Z
[INFO] ------------------------------------------------------------------------
Copying Maven artifacts from /tmp/src/target to /deployments ...
Running: cp -v *.jar /deployments
'spring-petclinic-2.3.1.BUILD-SNAPSHOT.jar' -> '/deployments/spring-petclinic-2.3.1.BUILD-SNAPSHOT.jar'
Checking for fat jar archive...
Found spring-petclinic-2.3.1.BUILD-SNAPSHOT.jar...
... done
Build completed successfully
Pro-tip: Incremental Build
If you were to run the build
command again you would notice that the build does EVERYTHING again. This includes downloading dependencies. By default, s2i
aims to provide a safe reproducible build. If you'd like to leverage caching of such resources (dependant on builder implementation) you may enable incremental builds via --incremental
.
s2i build https://github.com/spring-projects/spring-> petclinic fabric8/s2i-java pet-clinic --incremental
Run
Now that we've built our image we can run it just like we normally would any other image:
docker run -p 8080:8080 pet-clinic
We are binding port
8080
since that's the port our app uses by default.
Output
Here's the output of the petclinic app starting...
Starting the Java application using /opt/run-java/run-java.sh ...
exec java -javaagent:/opt/jolokia/jolokia.jar=config=/opt/jolokia/etc/jolokia.properties -javaagent:/opt/prometheus/jmx_prometheus_javaagent.jar=9779:/opt/prometheus/prometheus-config.yml -XX:+UseParallelGC -XX:GCTimeRatio=4 -XX:AdaptiveSizePolicyWeight=90 -XX:MinHeapFreeRatio=20 -XX:MaxHeapFreeRatio=40 -XX:+ExitOnOutOfMemoryError -cp . -jar /deployments/spring-petclinic-2.3.1.BUILD-SNAPSHOT.jar
I> No access restrictor found, access to any MBean is allowed
Jolokia: Agent started with URL http://172.17.0.2:8778/jolokia/
|\ _,,,--,,_
/,`.-'`' ._ \-;;,_
_______ __|,4- ) )_ .;.(__`'-'__ ___ __ _ ___ _______
| | '---''(_/._)-'(_\_) | | | | | | | | |
| _ | ___|_ _| | | | | |_| | | | __ _ _
| |_| | |___ | | | | | | | | | | \ \ \ \
| ___| ___| | | | _| |___| | _ | | _| \ \ \ \
| | | |___ | | | |_| | | | | | | |_ ) ) ) )
|___| |_______| |___| |_______|_______|___|_| |__|___|_______| / / / /
==================================================================/_/_/_/
:: Built with Spring Boot :: 2.3.1.RELEASE
2020-07-19 16:06:40.218 INFO 1 --- [ main] o.s.s.petclinic.PetClinicApplication : Starting PetClinicApplication v2.3.1.BUILD-SNAPSHOT on 4920727ddb55 with PID 1 (/deployments/spring-petclinic-2.3.1.BUILD-SNAPSHOT.jar started by jboss in /deployments)
2020-07-19 16:06:40.226 INFO 1 --- [ main] o.s.s.petclinic.PetClinicApplication : No active profile set, falling back to default profiles: default
2020-07-19 16:06:42.947 INFO 1 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFERRED mode.
2020-07-19 16:06:43.168 INFO 1 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 195ms. Found 4 JPA repository interfaces.
2020-07-19 16:06:44.925 INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2020-07-19 16:06:44.951 INFO 1 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2020-07-19 16:06:44.952 INFO 1 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.36]
2020-07-19 16:06:45.140 INFO 1 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2020-07-19 16:06:45.140 INFO 1 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 4797 ms
2020-07-19 16:06:46.331 INFO 1 --- [ main] org.ehcache.core.EhcacheManager : Cache 'vets' created in EhcacheManager.
2020-07-19 16:06:46.375 INFO 1 --- [ main] org.ehcache.jsr107.Eh107CacheManager : Registering Ehcache MBean javax.cache:type=CacheStatistics,CacheManager=urn.X-ehcache.jsr107-default-config,Cache=vets
2020-07-19 16:06:46.386 INFO 1 --- [ main] org.ehcache.jsr107.Eh107CacheManager : Registering Ehcache MBean javax.cache:type=CacheStatistics,CacheManager=urn.X-ehcache.jsr107-default-config,Cache=vets
2020-07-19 16:06:46.498 INFO 1 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2020-07-19 16:06:47.133 INFO 1 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
2020-07-19 16:06:47.609 INFO 1 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2020-07-19 16:06:48.127 INFO 1 --- [ task-1] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [name: default]
2020-07-19 16:06:49.586 INFO 1 --- [ task-1] org.hibernate.Version : HHH000412: Hibernate ORM core version 5.4.17.Final
2020-07-19 16:06:50.482 INFO 1 --- [ task-1] o.hibernate.annotations.common.Version : HCANN000001: Hibernate Commons Annotations {5.1.0.Final}
2020-07-19 16:06:51.609 INFO 1 --- [ task-1] org.hibernate.dialect.Dialect : HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
2020-07-19 16:06:54.376 INFO 1 --- [ task-1] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2020-07-19 16:06:54.432 INFO 1 --- [ task-1] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2020-07-19 16:06:54.523 INFO 1 --- [ main] o.s.b.a.e.web.EndpointLinksResolver : Exposing 13 endpoint(s) beneath base path '/actuator'
2020-07-19 16:06:54.605 INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2020-07-19 16:06:54.608 INFO 1 --- [ main] DeferredRepositoryInitializationListener : Triggering deferred initialization of Spring Data repositories?
2020-07-19 16:06:56.055 INFO 1 --- [ main] DeferredRepositoryInitializationListener : Spring Data repositories initialized!
2020-07-19 16:06:56.073 INFO 1 --- [ main] o.s.s.petclinic.PetClinicApplication : Started PetClinicApplication in 16.736 seconds (JVM running for 18.197)
^C2020-07-19 16:09:43.051 INFO 1 --- [extShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2020-07-19 16:09:43.056 INFO 1 --- [extShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'
2020-07-19 16:09:43.057 INFO 1 --- [extShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated...
2020-07-19 16:09:43.074 INFO 1 --- [extShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.
2020-07-19 16:09:43.085 INFO 1 --- [extShutdownHook] org.ehcache.core.EhcacheManager : Cache 'vets' removed from EhcacheManager.
Verify
Lastly, let's verify that our app is running by opening our browser to http://localhost:8080
That's it. With one simple command we've converted our java app source code to an OCI image.
This OCI image can then be pushed to any container registry (DockerHub, ECR, GCR, ACR, etc) and ran in any platform that supports OCI images such as #kubernetes.
Pitfalls
Below are a few pitfalls to consider when using s2i
. These may very well be moot points if they are resolved using the --runtime-image
option. Something I plan to explore further in another post.
Source leak
Depending on your requirements this may or not be an issue but given the way the basic process works the source code is persisted on the final app image. This not only increases the size unnecessarily but also exposes information that you otherwise might not have liked to be exposed.
Build tools leak
This is very well dependent on the builder to a certain extent. s2i build
without providing a separate run image from the build image means that all the tools necessary for building the application are carried over to the app image.
There are two immediate concerns:
- From a security perspective, the additional build tools may increase the potential attack vectors.
- From an optimization perspective, the image size is unnecessarily larger increasing storage space usage and transport data and time.
Update
07/22/2020: A new post is up that goes into more detail about how to resolve these pitfalls:
Top comments (0)