Skip to content

Commit

Permalink
Hi!
Browse files Browse the repository at this point in the history
  • Loading branch information
zimbatm committed Dec 5, 2018
1 parent c43a078 commit 41f18af
Show file tree
Hide file tree
Showing 24 changed files with 1,017 additions and 0 deletions.
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Nix
result
result-*

# Terraform
*.tfstate
*.tfstate.backup
.terraform
.terraform.*

91 changes: 91 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Terraform and Nix integration

[![built with nix](https://builtwithnix.org/badge.svg)](https://builtwithnix.org)

This repository contains a set of Terraform Modules designed to deploy NixOS
machines. These modules are designed to work together and support different
deployment scenarios.

## What is Terraform?

[Terraform][terraform] is a tool that allows to declare infrastructures as
code.

## What is Nix, nixpkgs and NixOS?

[Nix][nix] is a build system and package manager that allows to manage whole
system configurations as code. nixpkgs is a set of 20k+ packages built with
Nix. NixOS is a Linux distribution built on top of nixpkgs.

## What is a Terraform Module?

A Terraform Module refers to a self-contained packages of Terraform
configurations that are managed as a group. This repo contains a collection of
Terraform Modules which can be composed together to create useful
infrastructure patterns.

## Terraform + Nix vs NixOps

NixOps is a great tool for personal deployments. It handles a lot of things
like cloud resource creation, machine NixOS bootstrapping and deployment.

The difficulty is when the cloud resources are not supported by NixOps. It
takes a lot of work to map all the cloud APIs. Compared to NixOps, Terraform
has become an industry standard and has thousands of people contributing new
cloud API mapping all the time.

Another issue is when sharing the configuration as code with multiple
developers. Both NixOps and Terraform maintain a state file of "known applied"
configuration. Unlike NixOps, Terraform provides facilities to sync and lock
the state file so it's available by other users.

The approach here is to use Terraform to create all the cloud resources. By
using the `google_image_nixos_custom` module it's possible to pre-build images in
auto-scaling scenarios. Or use a push model similar to NixOps with the generic
`deploy_nixos` module.

So overall Terraform + Nix is more flexible and scales better. But it's also
more cumbersome to use as it requires to learn two languages instead of one
and the integration between both is also a bit clunky.

## Terraform Modules

The list of modules provided by this project:

* [deploy_nixos](deploy_nixos#readme) - deploy NixOS onto running NixOS
machines
* [google_image_nixos](google_image_nixos#readme) - setup an official GCE
image into a Google Cloud Project.
* [google_image_nixos_custom](google_image_nixos_custom#readme) - build and
deploy a custom GCE image into a Google Cloud Project

## Examples

To better understand how these modules can be used together, look into the
[./examples](examples) folder.

## Future

* Support other cloud providers.
* Support nixos-infect bootstrapping method.

Contributions are welcome!

## Thanks

Thanks to [Digital Asset][digital-asset] for generously sponsoring this work!

Thanks to [Tweag][tweag] for enabling this work and the continuous support!

## License

This code is released under the Apache 2.0 License. Please see
[LICENSE](LICENSE) for more details.

Copyright © 2018 Tweag I/O.


[digital-asset]: https://www.digitalasset.com/
[nix]: https://nixos.org/nix/
[terraform]: https://www.terraform.io
[tweag]: https://www.tweag.io/
97 changes: 97 additions & 0 deletions deploy_nixos/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# `deploy_nixos`

A Terraform module that knows how to deploy NixOS onto a target host.

This allow to describe an infrastructure as code with Terraform and delegate
the machine configuration with NixOS. All directed by Terraform.

The advantage of this method is that if any of the Nix code changes, the
difference will be detected on the next "terraform plan".

## Usage

Either pass a "config" which is a dynamic nixos configuration and a
"config_pwd", or a "nixos_config", a path to a nixos configuration.nix file.

### Secret handling

Keys can be passed to the "keys" attribute. Each key will be installed under
`/var/keys/${key}` with the content as the value.

For services to access one of the keys, add the service user to the "keys"
group.

The target machine needs `jq` installed prior to the deployment (as part of
the base image). If `jq` is not found it will try to use a version from
`<nixpkgs>`.

### Disabling sandboxing

Unfortunately some time it's required to disable the nix sandboxing. To do so,
add `["--option", "sandbox", "false"]` to the "extra_build_args" parameter.

If that doesn't work, make sure that your user is part of the nix
"trusted-users" list.

### Non-root `target_user`

It is possible to connect to the target host using a user that is not `root`
under certain conditions:

* sudo needs to be installed on the machine
* the user needs password-less sudo access on the machine

This would typically be provisioned in the base image.

## Dependencies

* `nix`

## Known limitations

The deployment machine requires Nix with access to a remote builder with the
same system as the target machine.

Because Nix code is being evaluated at "terraform plan" time, deploying a lot
of machine in the same target will require a lot of RAM.

All the secrets share the same "keys" group.

When deploying as non-root, it assumes that passwordless `sudo` is available.

The target host must already have NixOS installed.

### config including computed values

The module doesn't work when `<computed>` values from other resources are
interpolated with the "config" attribute. Because it happens at evaluation
time, terraform will render an empty drvPath.

see also:
* https://github.com/hashicorp/terraform/issues/16380
* https://github.com/hashicorp/terraform/issues/16762
* https://github.com/hashicorp/terraform/issues/17034

<!-- terraform-docs-start -->
## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|:----:|:-----:|:-----:|
| NIX\_PATH | Allow to pass custom NIX_PATH. Ignored if `-`. | string | `-` | no |
| config | NixOS configuration to be evaluated. This argument is required unless 'nixos_config' is given | string | `` | no |
| config\_pwd | Directory to evaluate the configuration in. This argument is required if 'config' is given | string | `` | no |
| extra\_build\_args | List of arguments to pass to the nix builder | list | `<list>` | no |
| extra\_eval\_args | List of arguments to pass to the nix evaluation | list | `<list>` | no |
| keys | A map of filename to content to upload as secrets in /var/keys | map | `<map>` | no |
| nixos\_config | Path to a NixOS configuration | string | `` | no |
| target\_host | DNS host to deploy to | string | - | yes |
| target\_user | SSH user used to connect to the target_host | string | `root` | no |
| triggers | Triggers for deploy | map | `<map>` | no |

## Outputs

| Name | Description |
|------|-------------|
| id | random ID that changes on every nixos deployment |

<!-- terraform-docs-end -->
133 changes: 133 additions & 0 deletions deploy_nixos/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
variable "target_user" {
description = "SSH user used to connect to the target_host"
default = "root"
}

variable "target_host" {
description = "DNS host to deploy to"
}

variable "NIX_PATH" {
description = "Allow to pass custom NIX_PATH. Ignored if `-`."
default = "-"
}

variable "nixos_config" {
description = "Path to a NixOS configuration"
default = ""
}

variable "config" {
description = "NixOS configuration to be evaluated. This argument is required unless 'nixos_config' is given"
default = ""
}

variable "config_pwd" {
description = "Directory to evaluate the configuration in. This argument is required if 'config' is given"
default = ""
}

variable "extra_eval_args" {
description = "List of arguments to pass to the nix evaluation"
type = "list"
default = []
}

variable "extra_build_args" {
description = "List of arguments to pass to the nix builder"
type = "list"
default = []
}

variable "triggers" {
type = "map"
description = "Triggers for deploy"
default = {}
}

variable "keys" {
type = "map"
description = "A map of filename to content to upload as secrets in /var/keys"
default = {}
}

# --------------------------------------------------------------------------

locals {
triggers = {
deploy_nixos_drv = "${data.external.nixos-instantiate.result.drv_path}"
deploy_nixos_keys = "${sha256(jsonencode(var.keys))}"
}

target_system = ["--argstr", "system", "x86_64-linux"]
attr_path = ["--attr", "system"]
}

# used to detect changes in the configuration
data "external" "nixos-instantiate" {
program = [
"${path.module}/nixos-instantiate.sh",
"${var.NIX_PATH}",
"${var.config != "" ? var.config : var.nixos_config}",
"${var.config_pwd != "" ? var.config_pwd : "."}",
"${local.target_system}",
"${local.attr_path}",
"${var.extra_eval_args}",
]
}

resource "null_resource" "deploy_nixos" {
triggers = "${merge(var.triggers, local.triggers)}"

connection {
type = "ssh"
host = "${var.target_host}"
user = "${var.target_user}"
agent = true
}

# copy the secret keys to the host
provisioner "file" {
content = "${jsonencode(var.keys)}"
destination = "packed-keys.json"
}

# FIXME: move this to nixos-deploy.sh
provisioner "file" {
source = "${path.module}/unpack-keys.sh"
destination = "unpack-keys.sh"
}

# FIXME: move this to nixos-deploy.sh
provisioner "file" {
source = "${path.module}/maybe-sudo.sh"
destination = "maybe-sudo.sh"
}

provisioner "remote-exec" {
inline = [
"chmod +x unpack-keys.sh maybe-sudo.sh",
"./maybe-sudo.sh ./unpack-keys.sh ./packed-keys.json",
]
}

# do the actual deployment
provisioner "local-exec" {
interpreter = [
"${path.module}/nixos-deploy.sh",
"${data.external.nixos-instantiate.result.drv_path}",
"${var.target_user}@${var.target_host}",
"switch",
"${var.extra_build_args}",
]

command = "ignoreme"
}
}

# --------------------------------------------------------------------------

output "id" {
description = "random ID that changes on every nixos deployment"
value = "${null_resource.deploy_nixos.id}"
}
11 changes: 11 additions & 0 deletions deploy_nixos/maybe-sudo.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env bash
#
# Run sudo if required
#
# Usage: ./maybe-sudo.sh <command> [...args]
set -euo pipefail
if [[ "$UID" = 0 ]]; then
exec -- "$@"
else
exec sudo -- "$@"
fi
Loading

0 comments on commit 41f18af

Please sign in to comment.