Skip to content

Brave compose#83

Merged
idroz merged 19 commits intobravetools:masterfrom
Szubie:brave-compose
Aug 19, 2022
Merged

Brave compose#83
idroz merged 19 commits intobravetools:masterfrom
Szubie:brave-compose

Conversation

@Szubie
Copy link
Collaborator

@Szubie Szubie commented Jun 10, 2022

Add a new brave compose command which reads a brave-compose.yml file and spins up the system of containers defined in it. Defining the system in one place like this makes it much easier to see/manage large deployments made up of multiple containers. In addition, it allows for a higher level of abstraction by thinking of multiple containers as a single deployment.

Integration with Bravefiles

brave-compose.yaml files integrate well with the existing Bravefiles, allowing the option for default unit settings to be loaded from bravefiles. This allows for the brave-compose.yaml file to remain lean and provide a high-level overview of the system. Settings specified in the brave-compose.yaml file will override those in the provided Bravefile (if any).

Optional Build

Although the focus of the compose tool is on deployment, there is also an optional build flag in the brave-compose.yaml file allowing for users to build entire systems and deploy them in a single command. All units and images from a build will be cleaned up if an issue is encountered, encouraging treating the set of containers as a single entity.

Changes to bravetools

To make these changes I narrowed the scope of the deployment functions InitUnit and Postdeploy to accept shared.Service structs instead of the whole Bravefile. This change mostly has no effect, but one thing does change - currently the Base unit name name is stored in the DB in UnitData. After this change, the actual Image the unit was derived from name will stored instead. This actually makes more sense to me - multiple images could be based on the same base unit, so it's more important to store the actual image a unit is deployed from.

TODO

  • Example script with example compose.yaml file
  • Documentation still needs to be added
  • Dependencies between units to determine deployment order

@adrozdovbering
Copy link
Contributor

Hi Ben. It would be very useful to have a test brave-compose.yml file and a test script in test folder. Could you please add them. Thank you!

@Szubie
Copy link
Collaborator Author

Szubie commented Jun 17, 2022

Hi Ben. It would be very useful to have a test brave-compose.yml file and a test script in test folder. Could you please add them. Thank you!

I am still working on a reproducible script and brave-compose.yml to show an example of the compose command in action, will add them when possible.

Just as a quick update: I've added support for dependencies between services in the compose file - the services will be topologically ordered to ensure that each service is built after the services it depends on. Tests for the topological ordering pass, so it appears to work as expected - let me know if I missed any cases.

@Szubie Szubie force-pushed the brave-compose branch 5 times, most recently from b36c9e7 to cd5727d Compare June 22, 2022 14:28
@Szubie
Copy link
Collaborator Author

Szubie commented Jun 24, 2022

Switching build/deploy directory

A further update: I realized that the majority of service Bravefiles will be written from an individual per-component basis and will rely on copying resources into containers from the local dir of that service. When composing these individual components together from a higher directory the relative paths would be incorrect.

To address this, brave compose now by default switches into the directory containing the Bravefile of the service if that is provided when building and deploying an image. This should be the most common case and so should prioritised. If for some reason that needs to be overriden, the new "context" field allows users to define a different working directory for building/deploying on a per-service basis.

Next steps

While working on reproducible examples for brave compose I ran into the following blocking issues:

  • Difficulties detaching from a server process if starting that service in Postdeploy Run command. Without detaching, deploy will stop on the first server started. But attempting to start a process in the background that keeps running was hard.
  • Passing environment variables into containers before running commands was also a problem.

The first problem is much more severe, as it essentially blocks the use of Compose for spinning up multiple servers. The second is less serious but could probably be addressed along with the first. The primary issue seems to be with how the LXD client (lxc) is sending commands currently in bravetools. I have some ideas on how to make the above cases work with a few additions/changes to command execution.

@Szubie
Copy link
Collaborator Author

Szubie commented Jun 30, 2022

Hi Ben. It would be very useful to have a test brave-compose.yml file and a test script in test folder. Could you please add them. Thank you!

I've added an example of using brave compose to build and deploy three simple python web services to the examples folder. The three services communicate with each other and are exposed on bravetools host through the API. The "brave-compose.yml" file used in the example is minimal - most of the settings are drawn from the Bravefiles.

@Szubie Szubie force-pushed the brave-compose branch 2 times, most recently from f893eef to 63f2434 Compare July 14, 2022 15:55
@Szubie
Copy link
Collaborator Author

Szubie commented Jul 14, 2022

Changes since I last checked in:

  • Test for compose added to host_api_test.go
  • Docs for brave compose added
  • Switching to brave-compose.yaml directory before build/deploy to make relative bravefile paths work
  • Loading of all Bravefiles happens on compose file load; this prevents errors from incorrect bravefile paths interrupting a deployment as the error will be caught up front.

I'm happy with the current state of things and think it's ready to merge into master.

Potential future additions might be worth considering later, such as brave compose sub-commands that help manage deployments. For example:

  • brave compose up
  • brave compose down
  • brave compose remove

These additions belong in a separate pull request however - the functionality present in the current PR (and its size) is enough to merge it as it is.

@Szubie
Copy link
Collaborator Author

Szubie commented Jul 22, 2022

Updates:

Skip build phase for existing images

Recent additions to bravetools now raise an error if an image already exists. I've updated brave compose to handle this in what I think is the best way for the tool.

If a service's "build" field is set to true and the image already exists, compose will skip building the image and continue with the rest of the compose operations (deploy etc.). This makes it easy to leave the build flag set to true in the compose file regardless of the state of the system, with it acting as a "build if image doesn't exist" flag. This seems better than the alternative (requiring manually tweaking the compose file to turn off build after first building the images).

"base" field within compose file

This one is speculative, but thought it was worth considering. Sometimes several images might share the same base dependencies, and it would be nice to be able to reuse the same base image as a starting point. It is already possible to first build a base image and have other builds import that local image, but it's a hassle to do and relies on the existing state of the system.

The base field makes this kind of thing possible within a compose file. A service with "base" set to true is not deployed, only built. In addition, since it is primarily a way of accelerating builds, by default a "base" image will automatically be cleaned up after a build - but if you want to keep it, you may set the "build" field to true. Services that depend on the base image can import it as a local image in their Bravefiles - in the compose file they can also express their dependency on the base image.

If every image depending on a base image already exists, the base image build will also be skipped as it is not required.

An example using the base idea to reuse a base image between 3 services has been added to the examples folder - in this very basic case it speeds up the build by 25% on my machine (saving ~1 minute) but as cost of building the base image increases the bigger the time savings will be as you'll only need to build it once instead of multiple times (offsetting the fixed cost to importing/exporting a new image).

Let me know if you think this is worth adding.

@adrozdovbering
Copy link
Contributor

adrozdovbering commented Jul 23, 2022

This is where I think that compose shouldn't be about "build", but rather, about "create a new unit based on image and then stand up the system". It feels like having image build and compose declared in one place mixes different concepts. Here's what I thought originally:

  1. define and build an image. Image can be a) a simple OS, b) prebuilt packages, c) custom built application
    Images are stored in the local or remote repository. Image can have different versions.

  2. Use image from repository as-it-is or modify it as needed for deployment. This is where "post-deploy" declarations are coming in. This can be declared in "compose" to build a custom system.

  3. There's option "publish" which can export newly customised and deployed unit as a brand new image that can be used in the future as "a base".

Last thought - having various flags, in my opinion, would bring unnecessary complexity and might lead to more complex logic in the Bravetools code.

What do you think?

@Szubie
Copy link
Collaborator Author

Szubie commented Jul 25, 2022

Thanks for you thoughts @adrozdovbering, it is good to have some feedback. I appreciate the vision of bravetools handling multiple remotes for both image storage and for deploy and also see it as something we need to work towards.

It would also be good to keep the number of flags low if possible! I felt that the "build" and "base" flags required only minimal changes to the code, are not too much to remember, and are justified by their contributions.

Build

I agree that primarily compose should focus more on deploy over build - this is why only "Service" fields are allowed in the compose file. Builds are only allowed when a Bravefile for that service has been provided, keeping the logic of the build process separate. Seeing as the Bravefile is present, it makes sense to be able to (optionally) use it if needed to facilitate the deployment of a service.

In my view having this option to build and deploy using a single command is in-line with the existing Bravefile structure of having build and deploy defined together. It fits in nicely with the idea of compose operating on a higher level, aiding with the process of managing deployments of multiple services by building on top of the lower level commands. It enables scaling up to build a large number services before deploying them, a proceses that otherwise requires manual work (or a script).

Base

The workflow mentioned regarding using build with postdeploy commands, followed by publish and then import and build is an interesting idea - I had not considered doing this! It is quite a different way of managing multi-stage builds. In my opinion it is quite a manual process, plus the order of builds matters and must be considered by the user. The intention is less obvious just from reading the Bravefile.

The strength of handling multi-stage builds in the brave-compose file is that the dependencies between images and their descendants are formally expressed in a declarative way and clearly visible in a single file at a glance - there's no need to read multiple Bravefiles to work out the ordering. In addition, since the order of dependencies is resolved by Bravetools, building a images that depend upon others is more reproducible, removing the possibility of manual errors or missing local images.

Alternatives?

I think the value provided by having these functions in brave compose is clear, but the best way to expose them is still a question. It may be that the idea I mentioned before of brave compose subcommands (like brave compose build?) may be another way of doing this.

But in a way I like the behaviour of the existing set up - it makes deployments truly reproducible by eliminating the need to consider the state of the server before running the command.

@Szubie Szubie force-pushed the brave-compose branch 2 times, most recently from c87f280 to 3f160b6 Compare July 28, 2022 15:11
Szubie added 6 commits August 15, 2022 15:07
…and CheckResources) functions to just the service component. These functions do not need access to the other fields in the bravefile.
… the provided Bravefile path. Settings specified in the composefile will override those in the bravefile.
…topological ordering of the service keys based on their dependencies to each other. If no valid ordering is found (indicative of a cycle in the graph), an error is returned.
Szubie added 13 commits August 15, 2022 15:07
… new

field "context" that will be used instead if provided. If neither
bravefile nor context provided, use current dir.
switching directories in Compose, call filepath.Abs to clean paths.
…h service. In addition each ComposeService now stores the loaded Bravefile in a new BravefileBuild field (not serialized via yaml). As a result, errors in Bravefile loading will be caught early - before potentially building and then deploying several other services. In addition, storing ComposeService pointers in service map merges loaded service information from Bravefile into original composefile struct, making it usable from caller.
…voured over .yml when possible. Changed examples and tests to use the .yaml extension.
…exists, skip build and continue with compose.
…are automatically only used as base images during a compose build phase. They are not deployed, and they are automatically deleted after compose completes (unless the field Build is set to true). This can enable large speedups in building multi-service systems if several of the services reuse the same underlying enviornment with a few tweaks.
…cess of spinning up a system with compose. Add base field details to compose.md doc file.
@idroz idroz merged commit a493ae0 into bravetools:master Aug 19, 2022
@Szubie Szubie deleted the brave-compose branch August 19, 2022 15:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants