Top Banner
Daniel Brown & Mario Camou Scala, Docker and Testing oh my! [Road]
67

Scala, docker and testing, oh my! mario camou

Jan 21, 2018

Download

Technology

J On The Beach
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Scala, docker and testing, oh my! mario camou

Daniel Brown & Mario Camou

Scala, Dockerand Testing

oh my!

[Road]

Page 2: Scala, docker and testing, oh my! mario camou

Who are we?Daniel Brown

Software engineer at eBay

Tinkerer and hacker of electronic devices

Resident mad scientist

@_dlpb / https://github.com/dlpb

Mario Camou

Software engineer at eBay

3D printing enthusiast and Doctor Who fan

“Do what I do. Hold tight and pretend it’s a plan!”

—The Doctor, Season 7, Christmas Special

@thedoc / https://github.com/mcamou/

Page 3: Scala, docker and testing, oh my! mario camou

Agenda● Introduction to Docker● Why would you use Docker in tests?● How do you integrate Docker into a Scala project?● Lessons learned

Page 4: Scala, docker and testing, oh my! mario camou

Intro to Docker

[Docker]

Page 5: Scala, docker and testing, oh my! mario camou

Virtual Machines● eBay’s default way of working● Easy to provision (through a UI)● In the “cloud”● Choice of OS’s

Page 6: Scala, docker and testing, oh my! mario camou

Virtual Machines● Slow to provision internally● Both API and UI● Machines can, and do, disappear● Internal connection latency

Page 7: Scala, docker and testing, oh my! mario camou

Virtual Machines● Time consuming to set up● Not possible easily to automate setup● e.g. databases, web servers,

message queues (even with chef)

Page 8: Scala, docker and testing, oh my! mario camou

Enter DockerDocker • /ˈdɒkər/

Noun

An open platform for developers and sysadmins to build, ship and run applications“

[Docker]

Page 9: Scala, docker and testing, oh my! mario camou

Enter DockerDependencies

● Create images of dependencies

[Docker]

Page 10: Scala, docker and testing, oh my! mario camou

Enter DockerDependencies

● Create images of dependencies● Doesn't solve all setup issues● But only needs to be done once

[Docker]

Page 11: Scala, docker and testing, oh my! mario camou

Enter DockerDependencies

● Create images of dependencies● Doesn't solve all setup issues● But only needs to be done once● Less storage overhead than VM● Databases and webservers = GBs of

data

[Docker]

Page 12: Scala, docker and testing, oh my! mario camou

Enter DockerDependencies

WIN!

[Docker]

Page 13: Scala, docker and testing, oh my! mario camou

Welcome toTheReal World

[Bird]

Page 14: Scala, docker and testing, oh my! mario camou

Background: Our Service

[MFS]

Page 15: Scala, docker and testing, oh my! mario camou

Background: Our Service

[MFS]

Page 16: Scala, docker and testing, oh my! mario camou

Background: Our Goal

Decouple our tests from other services

[MFS]

Page 17: Scala, docker and testing, oh my! mario camou

Dependency Nightmares

Untested

Unreliable

Slow

Fire

Page 18: Scala, docker and testing, oh my! mario camou

Isolation is Key● Decompose the monolith● Identify services we are dependent upon● Stub the contract● Run your tests

[MFS]

Page 19: Scala, docker and testing, oh my! mario camou

Decompose the Monolith

[Stonehenge]

Page 20: Scala, docker and testing, oh my! mario camou

The MonolithA common way of working

● Everything is “in the library”● Antipatterns● Black Magic

Page 21: Scala, docker and testing, oh my! mario camou

Identify Services we are Dependent UponThis can be the tricky step when everything is “in the framework”

● May require the use of debuggers, network sniffers etc● Investment is worth it in the long run

Page 22: Scala, docker and testing, oh my! mario camou

Identify Services we are Dependent UponMove configuration of services out to easily controllable (and versionable) config

● Implement, or update, a client to use the new config● Decoupling production from tests

Page 23: Scala, docker and testing, oh my! mario camou

Stub the ContractHere you need to know

● The API mappings● Expected Responses

Page 24: Scala, docker and testing, oh my! mario camou

Stub the ContractGolden Rule: Keep it Simple!

● Have one response per stub● Keep them versioned● Put them in containers

Page 25: Scala, docker and testing, oh my! mario camou

Run your TestsNow that we know our dependencies, contracts, and have stubs, we can write tests

● These are focussed only on testing that our system behaves as expected when downstream services offer varying responses

● We are NOT testing the downstream services

Page 26: Scala, docker and testing, oh my! mario camou

Run your Tests● Create a number of different stubs in different docker images● Spin up the ones you need for your specific test● When you are done with the test, tear them down and start again

Page 27: Scala, docker and testing, oh my! mario camou

So How do you go about it?So, how do we go about it?

[Hands]

Page 28: Scala, docker and testing, oh my! mario camou

Dockerizing Scala

[Cat]

Page 29: Scala, docker and testing, oh my! mario camou

Normal Docker flow● Create a Dockerfile● Build the Docker image● Push it to the registry● Pull the image from every server

Page 30: Scala, docker and testing, oh my! mario camou

Normal Docker flow● No static checking of the Dockerfile● Artifact build is separate from image build

○ Do you have the right artifacts?○ Did you run the tests before building?○ Are the latest bits in the image?

Page 31: Scala, docker and testing, oh my! mario camou

Integrating Docker with sbt

[Construction Kit]

Page 32: Scala, docker and testing, oh my! mario camou

Integrating Docker with sbtsbt-docker

An sbt plugin that:

● Creates a Dockerfile● Creates an image based on that file● Pushes the image to a registry

https://github.com/marcuslonnberg/sbt-docker

Page 33: Scala, docker and testing, oh my! mario camou

Setting the Image NameimageNames in docker := Seq( ImageName( namespace = Some("myOrg"), repository = name.value, tag = Some(s"v${version.value}") ), ImageName( namespace = Some("myOrg"), repository = name.value, tag = Some("latest") ))

Page 34: Scala, docker and testing, oh my! mario camou

Some Useful valsval artifact = (assemblyOutputPath in assembly).valueval baseDir = "/srv"val preInstall = Seq( "/usr/bin/apt-get update", s"/usr/sbin/useradd -r -s /bin/false -d $baseDir myUser").mkString(" && ")

Page 35: Scala, docker and testing, oh my! mario camou

Configuring the Dockerfiledockerfile in docker := { new Dockerfile { from("java:openjdk-8-jre") user("myUser") entryPoint("bin/run.sh") runRaw(preInstall) copy(new File("bin/run-docker.sh"), s"$baseDir/run.sh") copy(new File("local/etc"), "$baseDir/etc") copy(artifact, s"$baseDir/app.jar") runRaw("chown -R myUser $baseDir && chmod 0544 $baseDir/run.sh") }}

Page 36: Scala, docker and testing, oh my! mario camou

Integrating with sbt-assemblyEnsure assembly always runs before Docker

docker <<= (docker dependsOn assembly)

Page 37: Scala, docker and testing, oh my! mario camou

Creating and Publishing the Imagesbt> dockersbt> dockerPush

Page 38: Scala, docker and testing, oh my! mario camou

Caveats and Recommendations● Create a fat JAR: sbt-assembly or sbt-native-packager● In non-Linux platforms, start up docker-machine and set up the

environment variables before starting sbt:

$ docker-machine start theMachine$ eval $(docker-machine env theMachine)

https://velvia.github.io/Docker-Scala-Sbt/

Page 39: Scala, docker and testing, oh my! mario camou

[Explosion]

Page 40: Scala, docker and testing, oh my! mario camou

Integration Testing with DockerCreate Docker image(s) containing stubbed external resources

● Tests always run with clean data● Resource startup is standardized● Does not require multiple VMs or network calls

Page 41: Scala, docker and testing, oh my! mario camou

Integration Testing with DockerBefore your tests:

● Start up the stubbed resource containers● Wait for the containers to start

After your tests:

● Stop the containers

Can we automate all of this?

Page 42: Scala, docker and testing, oh my! mario camou

Container Orchestration during TestsOrchestration platforms

● Docker Compose● Kubernetes ● …

Page 43: Scala, docker and testing, oh my! mario camou

Container Orchestration during TestsOrchestration platforms

● Docker Compose● Kubernetes ● …

Orchestrate inside the test code

Page 44: Scala, docker and testing, oh my! mario camou

Using the Docker Java APIval config = DockerClientConfig.createDefaultConfigBuilder() .withServerAddress("tcp://192.168.99.100:2376") .withDockerCertPath("/path/to/certificates") .buildval docker = DockerClientBuilder.getInstance(config).buildval callback = new PullImageResultCallbackdocker.pullImageCmd("mongo:2.6.12").exec(callback)callback.awaitSuccessval container = docker.createContainerCmd("mongo:2.6.12") .withCmd("mongod", "--nojournal", "--smallfiles", "--syncdelay", "0") .execdocker.startContainerCmd(container.getId).execdocker.stopContainerCmd(container.getId).execval exitcode = docker.waitContainerCmd(container.getId).exec

Page 45: Scala, docker and testing, oh my! mario camou

Scala-native Solutionsreactive-docker and tugboat

● Use the Docker REST API directly -> versioning problems● Unmaintained for > 1 year (not updated to Docker 1.2 API)● No TLS support

Page 46: Scala, docker and testing, oh my! mario camou

Using reactive-dockerimplicit val docker = Docker("192.168.99.100", 2375)val timeout = 30.secondsval name = "mongodb-test"val cmd = Seq("mongod", "--nojournal", "--smallfiles", "--syncdelay", "0")val cfg = ContainerConfiguration(Some("mongo:2.6.12"), Some(cmd))val (containerId, _) = Await.result( docker.containerCreate("mongo:2.6.5", cfg, Some(name)), timeout)val started = Await.result(docker.containerStart(containerId), timeout)val stopped = Await.ready(docker.containerStop(containerId), timeout)

https://github.com/almoehi/reactive-docker

Page 47: Scala, docker and testing, oh my! mario camou

Caveats and Recommendations● Use beforeAll to ensure containers start up before tests● Use afterAll to ensure containers stop after tests

Caveats:

● Multiple container start/stops can make tests run much slower● Need to check when resource (not just container) is up● Single start/stop means testOnly/testQuick will start up all

resources● Ctrl+C will not stop the stubbed resource containers

Page 48: Scala, docker and testing, oh my! mario camou

Introducing docker-it-scala● Uses the (official) docker-java library● Starts up resource containers in parallel

○ Only when they are needed○ Once when the tests start○ Waits for service (not just container) startup

● Automatically shuts down all started stub containers● Configured via code or via Typesafe Config

https://github.com/whisklabs/docker-it-scala

[Docker-It-Scala]

Page 49: Scala, docker and testing, oh my! mario camou

Defining a Resource Container● Resources are declared as traits and mixed into tests● Sample implementations available for Cassandra, ElasticSearch,

Kafka, MongoDB, Neo4j, PostgreSQL, Zookeeper (in the docker-testkit-samples package)

[Docker-It-Scala]

Page 50: Scala, docker and testing, oh my! mario camou

Defining Resource Container (Neo4j)trait DockerNeo4jService extends DockerKit { val neo4jContainer = DockerContainer("whisk/neo4j:2.1.8") .withPorts(7474 -> None) .withReadyChecker( DockerReadyChecker.HttpResponseCode(7474, "/db/data/") .within(100.millis) .looped(20, 1250.millis) ))

abstract override def dockerContainers: List[DockerContainer] = neo4jContainer :: super.dockerContainers}

[Docker-It-Scala]

Page 51: Scala, docker and testing, oh my! mario camou

Defining Resource Container (PostgreSQL)docker { postgres { image-name = "postgres:9.4.4" environmental-variables = ["POSTGRES_USER=nph", "POSTGRES_PASSWORD=suitup"] ready-checker { log-line = "database system is ready to accept connections" } port-maps { Default-postgres-port.internal = 5432 } }}

[Docker-It-Scala]

Page 52: Scala, docker and testing, oh my! mario camou

Defining Resource Container (PostgreSQL)trait DockerPostgresService extends DockerKitConfig {

val postgresContainer = configureDockerContainer("docker.postgres")

abstract override def dockerContainers: List[DockerContainer] =

postgresContainer :: super.dockerContainers

}

[Docker-It-Scala]

Page 53: Scala, docker and testing, oh my! mario camou

Writing Your Testsclass MyMongoSpec extends FunSpec with DockerMongodbService { // Test assumes the MongoDB container is running}

class MyPostgresSpec extends FunSpec with DockerNeo4jService { // Test assumes the Neo4j container is running}

class MyAllSpec extends FunSpec with DockerMongodbService with DockerNeo4jService with DockerPostgresService{ // Test assumes all 3 containers are running}

https://github.com/whisklabs/docker-it-scala[Docker-It-Scala]

Page 54: Scala, docker and testing, oh my! mario camou

What Did We Achieve?

[Space needle]

Page 55: Scala, docker and testing, oh my! mario camou

The Effect● We cut our end to end testing time

120 minutes -> 10 minutes

[Clock]

Page 56: Scala, docker and testing, oh my! mario camou

The Effect● We cut our end to end testing time

120 minutes -> 10 minutes

● Confidence

[Clock]

Page 57: Scala, docker and testing, oh my! mario camou

Going Further● Performance Measurements● Business Decisions

Page 58: Scala, docker and testing, oh my! mario camou

Performance measurements (from Stubbed Tests)Create mocks that can simulate (or approximate) real-world conditions

● E.g.○ Drop every third request○ Delay 5 seconds before responding○ Respond instantly for every request

Page 59: Scala, docker and testing, oh my! mario camou

Performance measurements (from Stubbed Tests)Use these new stubs to gather data about how your system performs

● When it is stressed;● When downstream services are stressed

Page 60: Scala, docker and testing, oh my! mario camou

Performance measurements (from Stubbed Tests)Use these new stubs to gather data about how your system performs

● When it is stressed;● When downstream services are stressed

Gather the metrics!

Page 61: Scala, docker and testing, oh my! mario camou

Performance measurements (from Stubbed Tests)

[MFS]

Page 62: Scala, docker and testing, oh my! mario camou

Going FurtherWhat did we decide from the graph?

Page 63: Scala, docker and testing, oh my! mario camou

Going FurtherWhat did we decide from the graph?

● Technical limitations for our product

Page 64: Scala, docker and testing, oh my! mario camou

Going FurtherWhat did we decide from the graph?

● Technical limitations for our product● Business policies for the product

Page 65: Scala, docker and testing, oh my! mario camou

SummaryIsolation is key to gathering meaningful test data and keeping testing strategies sane

Docker can ease the pain of managing stub dependencies during integration testing

Meaningful tests can tell you a lot about the behaviour of your system and therefore influence both architecture and UX

Page 66: Scala, docker and testing, oh my! mario camou

Q & A

[Audience]

Page 67: Scala, docker and testing, oh my! mario camou

Acknowledgements & Notes[Docker] Docker: www.docker.comDocker and the Docker logo are trademarks or registered trademarks of Docker, Inc. in the United States and/or other countries. Docker, Inc. and other parties may also have trademark rights in other terms used herein.

[Fire], Fire image, Creative Commons CC0 License https://www.pexels.com/photo/fire-orange-emergency-burning-1749/

[Clock], Clock Image, Creative Commonshttps://www.flickr.com/photos/75680924@N08/6830220892/in/photostream/

[Space needle] Space needle, Creative Commons Zerohttps://unsplash.com/photos/-48aJfQpFCE

[Explosion] Vehicle and building on fire, Creative Commons Zerohttps://unsplash.com/photos/28v9cq7ytNU

[Stonehenge] Stonehenge, Public Domain

[Cat] Firsalar the fluffy cat loves to sit in boxes, CC-BY 2.0

[Construction Kit] Free Universal Construction Kit by F.A.T. Lab and Sy-Lab http://fffff.at/free-universal-construction-kit/

[MFS] While MFS was released to production for a short time, it was not released for external customer use.

[Audience] Audience, Creative Commons Zerohttps://unsplash.com/photos/bBQ9lhB-wpY

[Bird] Bird Stare, Creative Commons Zerohttps://unsplash.com/photos/ig9lRTGT0h8

[Road] Yellow Brick road to Lost Hatch, CC-BY-NC 2.0 https://www.flickr.com/photos/wvs/352414272

[Hands] Mud Hands, CC-BY-NC 2.0 https://www.flickr.com/photos/migueltejadaflores/13783685515

[Docker-it-scala] Docker IT Scala, Whisk Labs, MIT https://github.com/whisklabs/docker-it-scala