Skip to content

Allow user-defined host containers #386

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Oct 10, 2019
Merged

Allow user-defined host containers #386

merged 2 commits into from
Oct 10, 2019

Conversation

tjkirch
Copy link
Contributor

@tjkirch tjkirch commented Oct 9, 2019

Fixes #314


First commit, moving to HashMap in the model:

commit 87031a2bb72cd4242a20de012ac8cbad650a594f
Author: Tom Kirchner <[email protected]>
Date:   Fri Oct 4 10:12:24 2019 -0700

Switch to HashMap for host-containers keys

We don't need a custom struct for this.  A HashMap is represented the same
way on disk and by the ser/de code, and opens the way for custom host
containers in the map.  (That will require building a single
host-containers service to manage them all.)

Testing done:

Before, if you try to send in a custom container, you'll see it fail because it can't deserialize into the existing struct.

$ cargo run -- --socket-path /tmp/thar-api.sock -u /settings -X PATCH -d '{"host-containers": {"custom": {"source": "maybe-dockerhub", "enabled": false, "superpowered": false}}}' -v
400 Bad Request
Json deserialize error: unknown field `custom`, expected `admin` or `control` at line 1 column 29

After, you can send it:

$ cargo run -- --socket-path /tmp/thar-api.sock -u /settings -X PATCH -d '{"host-containers": {"custom": {"source": "maybe-dockerhub", "enabled": false, "superpowered": false}}}' -v
204 No Content

$ cargo run -- --socket-path /tmp/thar-api.sock -m POST -u /settings/commit_and_apply -v
200 OK
["settings.host-containers.custom.source","settings.host-containers.custom.superpowered","settings.host-containers.custom.enabled"]

And it shows up correctly; the output is otherwise identical, and it looks right on the filesystem:

$ cargo run -- --socket-path /tmp/thar-api.sock -u /settings -v | jq .
200 OK
{
...
  "host-containers": {
...
    "admin": {
      "source": "328549459982.dkr.ecr.us-west-2.amazonaws.com/thar-admin:v0.1",
      "enabled": false,
      "superpowered": true
    },
    "custom": {
      "source": "maybe-dockerhub",
      "enabled": false,
      "superpowered": false
    }
  },
...
}

Second commit, the service that uses the HashMap to actually start services:

commit ee5440d9f5ddfe62d9ebb4d2b16322be2f5f8b79 (HEAD -> host-containers, origin/host-containers)
Author: Tom Kirchner <[email protected]>
Date:   Fri Oct 4 13:08:23 2019 -0700

Allow user-defined host containers

This adds a host-containers service that asks the API for the defined
host-containers and tells systemd to start/stop them to match their
'enabled' setting.  The settings are written to an EnvironmentFile that's
read by the new host-container@ service, a systemd templated service used
for all host containers by way of a suffix like "host-container@admin".

The existing container-specific thar-be-settings templated systemd unit
files were removed in favor of this more general approach.  Existing
metadata for these was also removed, replaced with a single metadata entry
that invokes the new tool.

The existing host-containers package, which just contains the Go host-ctr
source, was renamed to host-ctr.

Testing done:

Built an AMI, SSM was fine (meaning control instance was fine) and started admin and logged in via SSH (meaning admin instance was fine).

Overall, systemctl is happy:

bash-5.0# systemctl status | head                                                                                                                                                  
● ip-192-168-109-102.us-west-2.compute.internal
    State: running
     Jobs: 0 queued
   Failed: 0 units
    Since: Thu 2019-10-10 18:56:09 UTC; 11min ago

I started the admin container after boot, and we can see both service instances happy:

bash-5.0# systemctl status host-containers@control
● [email protected] - Host container: control
   Loaded: loaded (/x86_64-thar-linux-gnu/sys-root/usr/lib/systemd/system/[email protected]; enabled; vendor preset: enabled)
   Active: active (running) since Thu 2019-10-10 18:56:45 UTC; 11min ago
 Main PID: 3157 (host-ctr)
    Tasks: 23
   Memory: 35.4M
   CGroup: /system.slice/system-host\x2dcontainers.slice/[email protected]
           └─3157 /usr/bin/host-ctr -ctr-id=control -source=328549459982.dkr.ecr.us-west-2.amazonaws.com/thar-control:v0.1 -superpowered=false

Oct 10 19:07:35 ip-192-168-109-102.us-west-2.compute.internal host-ctr[3157]:   "CwlStream": ""
...
bash-5.0# systemctl status host-containers@admin
● [email protected] - Host container: admin
   Loaded: loaded (/x86_64-thar-linux-gnu/sys-root/usr/lib/systemd/system/[email protected]; enabled; vendor preset: enabled)
   Active: active (running) since Thu 2019-10-10 18:56:45 UTC; 11min ago
 Main PID: 3156 (host-ctr)
    Tasks: 20
   Memory: 31.4M
   CGroup: /system.slice/system-host\x2dcontainers.slice/[email protected]
           └─3156 /usr/bin/host-ctr -ctr-id=admin -source=328549459982.dkr.ecr.us-west-2.amazonaws.com/thar-admin:v0.1 -superpowered=true

Oct 10 18:56:45 ip-192-168-109-102.us-west-2.compute.internal systemd[1]: Started Host container: admin.
Oct 10 18:56:45 ip-192-168-109-102.us-west-2.compute.internal host-ctr[3156]: time="2019-10-10T18:56:45Z" level=info msg="No clean up necessary, proceeding" ctr-id=admin
...

Which means the host-containers configs (used as EnvironmentFiles) were written correctly:

bash-5.0# cat /etc/host-containers/admin.env
CTR_SUPERPOWERED=true
CTR_SOURCE=328549459982.dkr.ecr.us-west-2.amazonaws.com/thar-admin:v0.1

# Just for reference; service is enabled or disabled by host-containers service
# CTR_ENABLED=true
bash-5.0# cat /etc/host-containers/control.env
CTR_SUPERPOWERED=false
CTR_SOURCE=328549459982.dkr.ecr.us-west-2.amazonaws.com/thar-control:v0.1

# Just for reference; service is enabled or disabled by host-containers service
# CTR_ENABLED=true

I also pushed a custom container entry, just another copy of the control container:

bash-5.0# apiclient -u /settings -X PATCH -d '{"host-containers": {"custom": {"source": "328549459982.dkr.ecr.us-west-2.amazonaws.com/thar-control:v0.1", "enabled": true, "superpowered": false}}}' -v 
204 No Content
bash-5.0# apiclient -u /settings/commit_and_apply -X POST
["settings.host-containers.custom.source","settings.host-containers.custom.superpowered","settings.host-containers.custom.enabled"]

We can see the service for it started correctly:

bash-5.0# systemctl status host-containers@custom
● [email protected] - Host container: custom
   Loaded: loaded (/x86_64-thar-linux-gnu/sys-root/usr/lib/systemd/system/[email protected]; enabled; vendor preset: enabled)
   Active: active (running) since Thu 2019-10-10 19:08:33 UTC; 4s ago
 Main PID: 6928 (host-ctr)
    Tasks: 18
   Memory: 16.8M
   CGroup: /system.slice/system-host\x2dcontainers.slice/[email protected]
           └─6928 /usr/bin/host-ctr -ctr-id=custom -source=328549459982.dkr.ecr.us-west-2.amazonaws.com/thar-control:v0.1 -superpowered=false

Oct 10 19:08:36 ip-192-168-109-102.us-west-2.compute.internal host-ctr[6928]: 2019-10-10 19:08:34 INFO [StartupProcessor] Executing startup processor tasks
...

We can also stop it:

bash-5.0# apiclient -u /settings -X PATCH -d '{"host-containers": {"custom": {"enabled": false}}}' -v                                                                                                   
204 No Content
bash-5.0# apiclient -u /settings/commit_and_apply -X POST
["settings.host-containers.custom.enabled"]
bash-5.0# systemctl status host-containers@custom               
● [email protected] - Host container: custom
   Loaded: loaded (/x86_64-thar-linux-gnu/sys-root/usr/lib/systemd/system/[email protected]; disabled; vendor preset: enabled)
   Active: inactive (dead)

We don't need a custom struct for this.  A HashMap is represented the same way
on disk and by the ser/de code, and opens the way for custom host containers in
the map.  (That will require building a single host-containers service to
manage them all.)
@tjkirch
Copy link
Contributor Author

tjkirch commented Oct 9, 2019

Sorry for the quick force-push, I forgot to do it before publishing; it's some fixes I discovered while testing.

@tjkirch
Copy link
Contributor Author

tjkirch commented Oct 9, 2019

Note that servicedog is no longer used with this change; host-containers takes its place for existing use cases. We want to keep servicedog for other uses that may be desired soon, per Ben, so I didn't stop it from building.

Copy link
Contributor

@iliana iliana left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think my favorite thing about this pull request is how well it demonstrates apiserver's flexibility; this adds functionality without changing the interface and with a simple change to the settings model. (I had to look and check that the defaults were still getting applied because you didn't have to change those.) 👏

Comment on lines 8 to 9
EnvironmentFile=/etc/host-containers/%i.env
ExecStart=/usr/bin/host-ctr -ctr-id=%i -source=${CTR_SOURCE} -superpowered=${CTR_SUPERPOWERED}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to sanitize %i value if we're using it in ways that could introduce a directory traveral. E.g. "../foo" as container name.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I understand fully. I should probably quote these, but I'm not sure how or why to sanitize them. "../foo" seems like a silly, but valid, name for something, and nothing should interpret it as a path. Same for the superpowered boolean; if it's not "true" or "false" it should fail to parse or be accepted. With source, any URI the host-ctr process has access to could theoretically be a valid container source, and if we don't want to allow local paths relative to the process, that's a security policy I think host-ctr or containerd should enforce, not the argument handling of a service unit.

This comment was marked as off-topic.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume Ben is more worried about the variables coming from the EnvironmentFile which come from settings the user can specify through the API.

@@ -33,6 +33,7 @@ skip = [
{ name = "bork", licenses = [] },
{ name = "data_store_version", licenses = [] },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unrelated nit: should this be data-store-version?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It matches the naming of the directory and crate, which were named with underscores because that's how you import it into other Rust code... not sure if that's the best solution...

@tjkirch
Copy link
Contributor Author

tjkirch commented Oct 10, 2019

This push addresses some of @bcressey 's comments:

  • Use tmpfilesd to create /etc/host-containers
  • Quote arguments in [email protected]
  • No longer include servicedog in release.spec
  • Continue managing other containers if one fails
  • Comment out CTR_ENABLED line in EnvironmentFile to further clarify it's for humans

I repeated the testing and updated the results.

@tjkirch
Copy link
Contributor Author

tjkirch commented Oct 10, 2019

This push adds the comment @bcressey requested, and adds a modeled type to verify that settings like container source, which are headed to configuration files, can't use multiple lines to inject environment settings.

Testing in description repeated and passed.

Here you can see multi-line strings failing. First, the client won't even accept control characters, which I just learned:

bash-5.0# apiclient -u /settings -X PATCH -d '{"host-containers": {"custom": {"enabled": true, "superpowered": true, "source": "evil                                               
> multiline
> string"}}}' -v
400 Bad Request
Json deserialize error: control character (\u0000-\u001F) found while parsing a string at line 2 column 0

Second, if I use escapes, we can see the new error:

bash-5.0# apiclient -u /settings -X PATCH -d '{"host-containers": {"custom": {"enabled": true, "superpowered": true, "source": "evil\nmultiline\nstring"}}}' -v
400 Bad Request
Json deserialize error: Can't create SingleLineString with line terminator '4' at line 1 column 107

Copy link
Contributor

@etungsten etungsten left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you give an example of how to specify a custom host container through userdata.toml and maybe add it to the top level README?

This adds a host-containers service that asks the API for the defined
host-containers and tells systemd to start/stop them to match their 'enabled'
setting.  The settings are written to an EnvironmentFile that's read by the new
host-container@ service, a systemd templated service used for all host
containers by way of a suffix like "host-container@admin".

The existing container-specific thar-be-settings templated systemd unit files
were removed in favor of this more general approach.  Existing metadata for
these was also removed, replaced with a single metadata entry that invokes the
new tool.

The existing host-containers package, which just contains the Go host-ctr
source, was renamed to host-ctr.
@tjkirch
Copy link
Contributor Author

tjkirch commented Oct 10, 2019

This push uses SingleLineString for the host container names, too.

@tjkirch tjkirch merged commit da07a88 into develop Oct 10, 2019
@tjkirch tjkirch deleted the host-containers branch October 10, 2019 23:29
Copy link
Contributor

@zmrow zmrow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is merged - just had a few comments.

})?;

info!(
"Handling host container '{}' which is enabled: {}",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This wording is a little weird which is understandable given the limitations of the variables. This might be just as bad but here's my suggestion:

"Handling host container '{}' with 'enabled' status: {}"

🤷‍♂

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.

User defined host containers
7 participants