Basic Setup#

Once you have a contract with Antithesis, you’ll need to get your software ready for our testing environment. This document walks you through the process of packaging your software for Antithesis so that you can run your first test.

1. Containerize your software#

Antithesis runs your software using Linux container images, similarly to how Docker and Kubernetes do it. So the first step is to build a container image that includes your software and any of its dependencies. If you operate a microservices architecture, you should provide a separate container for each of your services, though it’s fine if they share intermediate layers.

If you or your customers already deploy your software via containers, there’s a decent chance your existing images will work out of the box. However there are a few common gotchas that you should be aware of:

  • Within the Antithesis environment, your various containerized services will have network access to each other, but not to the broader internet. If your services perform network calls to the public web on startup, for example to fetch a software dependency or data file, you should move this into the container build process instead.

For example, DON’T do this:

FROM docker.io/ubuntu:latest
COPY my_app /opt/my_app
CMD curl --fail https://example.com/data > /opt/data && /opt/my_app /opt/data

Do this instead:

FROM docker.io/ubuntu:latest
COPY my_app /opt/my_app
RUN curl --fail https://example.com/data > /opt/data
CMD /opt/my_app /opt/data
  • If you have chosen to instrument your software, then depending on the language your software is written in, you may need to install additional runtime dependencies into your container. It’s fine not to worry about instrumentation for your very first run. Please see the langauge-specific instructions in the instrumentation section of our documentation for more.

  • Antithesis runs your software on x86-64 CPUs; please ensure that your software is compiled for this architecture.

2. Containerize your dependencies#

Antithesis runs your system in a hermetic simulation environment. Everything your system depends upon must also be deployed into that environment, or suitably mocked.

  • If your system depends on other first-party services that you ordinarily control and operate, for example a local database node, it’s generally easiest to just put those in additional containers and run them in the Antithesis simulation as well. In the next step, we will see how to use container orchestration technologies to enable your containers to discover each other.

  • If your system depends on third-party services that you cannot run yourself (such as an API controlled by another company), then it’s easiest either to disable this part of your system’s functionality, or to build a local mock that emulates its behavior. This mock can run alongside your software in the same container, or it can run in a separate container and make itself available over the network. The latter option enables Antithesis to perform fault injection on the connection between your software and the third-party service.

  • For some very popular third-party services, Antithesis has already built sophisticated local mocks or emulators, which you can just use with no additional effort. For example, we emulate a number of AWS APIs and services.

3. Containerize your workload#

A workload, or test harness, is the code that makes your software do something. See our workload guide for advice on writing an effective workload. Once it’s written, your workload must also be built into a container image. Your workload will run in a separate container from the services you’re testing, so that we can fault inject the connections between them.

If your workload makes use of the Antithesis SDK, you may need to install additional runtime dependencies into this container as well. See our SDK documentation for more information.

4. Create a configuration directory#

Now you’re going to create a directory in our local filesystem, called config, into which we will put the following items:

  • Any configuration files expected by the containers: license files, settings, or other resources.

  • Empty directories for any mountpoints of volumes that will be shared into the containers. You should create each of these under the common prefix volumes/

  • A container orchestration file (see next step).

There are two main reasons to use external volume mounts for your containers:

  • Storing data that needs to be durable even if the container is restarted (for example database files).

  • Storing logs that you want to have captured by Antithesis. In general, Antithesis will capture everything written to the container’s stdout and stderr streams, but if your service writes its logs to files instead, then these should be written to a volume that is mapped out of the container, so that Antithesis can still capture them.

Here are the contents of our example configuration directory:

$ ls -a config/
.
..
docker-compose.yaml
license
volumes

$ ls -a config/volumes
.
..
database
workload-logs

5. Set up container orchestration#

Antithesis supports a few different orchestration technologies, but the one that’s easiest to set up is Docker Compose. This involves creating a file called docker-compose.yml that lists each of the services you want to have running, the container image that service should be started from, any external volumes that should be mounted into that image, and other options. We support most of the options available in a Compose file.

Here’s an example:

version: '3.0'

services:
  application1:
    container_name: application1
    hostname: application1
    image: mycompany/app:antithesis-tag
    networks:
      antithesis-net:
        ipv4_address: 10.20.20.1

  application2:
    container_name: application2
    hostname: application2
    image: mycompany/app:antithesis-tag
    networks:
      antithesis-net:
        ipv4_address: 10.20.20.2

  database:
    container_name: database
    hostname: database
    image: docker.io/mysql:latest
    volumes:
      - ./volumes/database:/usr/bin/database/data
    networks:
      antithesis-net:
        ipv4_address: 10.20.20.3

  workload:
    container_name: workload
    hostname: workload
    image: mycompany/workload:antithesis-tag
    volumes:
      - ./volumes/workload-logs:/usr/bin/workload/logs
    networks:
      antithesis-net:
        ipv4_address: 10.20.20.128

networks:
  antithesis-net:
    driver: bridge
    ipam:
      config:
      - subnet: 10.20.20.0/24

A few items to note in the above example:

  • Two application services are being run from the same underlying container image. In general, Antithesis is most useful for testing high-availability configurations of your service. For example, we can try things like the following: we can cause one of the application servers to fail while the other remains up and see if the workload/client is able to reconnect successfully.

  • Some of the container images are coming from your internal container registry, and others (the MySQL dependency) are coming from a public Docker Hub repository. We support either option.

  • Two of the services have requested external volume mounts. Data stored in these directories will be mapped out of the container filesystem, and will outlive the container’s own lifetime. The volume declarations are referring to the empty directories created in the configuration directory (see above). Since we are going to use the configuration directory as our working directory, these relative paths will resolve correctly.

  • The example explicitly defines a network and allocates static IPs to each of the services running on that network; both of these choices are optional.

  • In addition to network address, the services in the above example can reach each other by hostname. Our container orchestration will automatically generate suitable DNS entries from the names of each of the services, and inject them into the environment of every other service.

Contact us if you need help setting up suitable container orchestration.

6. Validating what you have so far#

If you’ve followed the steps above, you should now be able to test your entire setup locally before deploying it to the Antithesis environment. Running docker-compose up from the root of your configuration directory should produce a running system with a connected workload.

However, we want to test that this all still works without any access to the internet, because Antithesis does not provide external internet access. The easiest way to do this is to first enter into a network namespace before running the command:

$ pwd
/home/user/config

$ unshare -n

[2] $ docker-compose up
...
[Lots of output]
...

If everything still comes up and works, your services all find each other, and your workload begins running, all when run without access to the internet, then we’re nearly there!

7. Building a configuration image#

Now that everything works locally when run from within your configuration directory, the only thing left is to ship the contents of your configuration directory to Antithesis as well. Since you’re already going to be sending us a bunch of container images, the easiest thing to do is to create one more container image to hold the contents of the configuration directory, and send us that as well. We call this the “configuration image”.

A Dockerfile that copies the configuration files and adds the directories into a scratch image is generally sufficient:

FROM scratch
COPY config/docker-compose.yml /docker-compose.yml
ADD config/license /license
ADD config/volumes/database /volumes/database
ADD config/volumes/workload-logs /volumes/workload-logs

If you’ve enabled instrumentation for your software, you may also need to add “symbol files” created by the instrumentor or by your build toolchain to a special location in the configuration image. Please see the langauge-specific instructions in the instrumentation section of our documentation for more.

8. Push your containers#

Antithesis has configured a container registry for you. If your usage tier qualifies you for enhanced tenant isolation, then this is literally run on separate hardware from every other tenant, so that even a bug in our systems or configuration would not be sufficient to enable other users to read your container data. As part of your Antithesis onboarding, we sent you a credential file $TENANT_NAME.key.json.

To authenticate to your container registry, run the following command:

$ cat $TENANT_NAME.key.json | docker login -u _json_key https://us-central1-docker.pkg.dev --password-stdin

Now you are locally authenticated to the registry, and can run all other docker commands as normal.

The registry you should push to is located at: us-central1-docker.pkg.dev/molten-verve-216720/$TENANT_NAME-repository/

If you have a local image named app, you can tag and push it as follows:

$ docker tag app us-central1-docker.pkg.dev/molten-verve-216720/$TENANT_NAME-repository/app:antithesis-latest
$ docker push us-central1-docker.pkg.dev/molten-verve-216720/$TENANT_NAME-repository/app:antithesis-latest

Here, antithesis-latest is just an example of a tag that you could use. If you have multiple Git branches, test configurations, or environments that you want to test, you should pick a different tag for each of them. However, it’s important that every container image within a given branch, test configuration, or environment use the same tag. When Antithesis begins running your test, it will pull all of your containers with a given tag.

9. Wrapping up#

That’s it! You now have basic integration with Antithesis. From here, we recommend running some tests and iterating with us on the results. When you’re satisfied by the setup, you should configure your CI system to automatically build and push the above container images every night.

Contact us if you have questions.