-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy path03_containers.qmd
More file actions
305 lines (195 loc) · 14.4 KB
/
Copy path03_containers.qmd
File metadata and controls
305 lines (195 loc) · 14.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
---
title: "Week 3: Containers"
format: html
---
## Containers {#sec-containers}
We already learned about software modules (@sec-modules) on the `gizmo` cluster. There is an alternative way to use software: pulling and running a software {{<glossary "container">}}.
### What is a Container?
A container is a self-contained unit of software. It contains everything needed to run the software on a variety of machines. If you have the container software installed on your machine, it doesn't matter whether it is MacOS, Linux, or Windows - the container will behave consistently across different operating systems and architectures.
The container has the following contents:
- **Software** - The software we want to run in a container. For bioinformatics work, this is usually something like an aligner like `bwa`, or utilities such as `samtools`
- **Software Dependencies** - various software packages needed to run the software. For example, if we wanted to run `tidyverse` in a container, we need to have `R` installed in the container as well.
- **Filesystem** - containers have their own isolated filesystem that can be connected to the "outside world" - everything outside of the container. We'll learn more about customizing these with bind paths (@sec-bindpaths).
In short, the container has everything needed to run the software. It is not a full operating system, but a smaller mini-version that cuts out a lot of cruft.
Containers are {{< glossary "ephemeral">}}. They leverage the the file system of their host to manage files. These are called both *Volumes* (the Docker term) and *Bind Paths* (the apptainer term).
### Docker vs. Apptainer
There are two basic ways to run Docker containers:
1. Using the Docker software
2. Using the Apptainer software (for HPC systems)
In general, Docker is used on systems where you have a high level of access to the system. This is because `docker` uses a special user group called `docker` that has essentially root level privileges. This is not something to be taken lightly.
This is not the case for HPC systems, which are shared and granting this level of access to many people is not practical. This is when we use {{< glossary "Apptainer">}} (which used to be called Singularity), which requires a much lower level of user privileges to execute tasks. For more info, see @sec-open-container .
:::{.callout-warning}
## Be Secure
Before we get started, security is always a concern when running containers. The `docker` group has elevated status on a system, so we need to be careful that when we're running them, these containers aren't introducing any system vulnerabilities. Note that on HPC systems, the main mechanism for running containers is `apptainer`, which is designed to be more secure.
These are mostly important when running containers that are web-servers or part of a web stack, but it is also important to think about when running jobs on HPC.
Here are some guidelines to think about when you are working with a container.
- **Use vendor-specific Docker Images when possible**.
- **Use container scanners to spot potential vulnerabilities**. DockerHub has a vulnerability scanner that scans your Docker images for potential vulnerabilities. For example, the WILDS Docker library employs a vulnerability scanner and the containers are regularly patched to prevent vulnerabilities.
- **Avoid kitchen-sink images**. One issue is when an image is built on top of many other images. It makes it really difficult to plug vulnerabilities. When in doubt, use images from trusted people and organizations. At the very least, look at the Dockerfile to see that suspicious software isn't being installed.
:::
## Pulling a Docker Container and Running It
Let's pull a docker container from the Docker registry. Note we have to specify `docker://` when we pull the container, because Apptainer has its own internal format called SIF.
```bash
module load Apptainer/1.1.6
apptainer pull docker://biocontainers/samtools:v1.9-4-deb_cv1
apptainer exec \
--bind /path/to/data:/data \ #<1>
docker://biocontainers/samtools:v1.9-4-deb_cv1 \
samtools view -c /mydata/$1 > /mydata/$1.counts.txt
```
### Using a Container
In @sec-containers, we learned a little bit about using Apptainer to run a Docker container. Let's try to pull a common container, the Genome Analysis Toolkit (GATK) and run things inside the container.
The first thing we need to do is load Apptainer:
```bash
module load Apptainer/1.1.6
```
Then we can pull the docker container:
```bash
apptainer pull docker://biocontainers/samtools:v1.9-4-deb_cv1
```
We can check if we have pulled the docker image by using
```bash
apptainer cache list
```
Okay, now we have confirmed that we downloaded the apptainer image. Now we can try to execute things with it.
```bash
apptainer exec \
--bind /path/to/data:/data \ #<1>
docker://biocontainers/samtools:v1.9-4-deb_cv1 \ # <2>
samtools view -c /mydata/$1 > /mydata/$1.counts.txt # <3>
```
1. Bind path (see @sec-bindpaths)
2. Docker image we have downloaded
3. `samtools` command to run.
It's worth trying this once to make sure you understand how all of the pieces are connected. In general, I do recommend using a workflow runner (@sec-workflows) instead, because it helps manage all of these details, and it makes reading files easier.
### Bind Paths
One thing to keep in mind is that containers have their own filesystem. They can only read and write to folders in the external filesystem that you give them access to with *bind paths*. The one exception is the current working directory.
For more info about bind paths see @sec-bindpaths.
## Testing code in a container {#sec-open-container}
I think the hardest thing about working with containers is wrapping your head around the indirectness of them. You are running software with its own internal filesystem and the challenges are getting the container to read files in folders/paths outside of its own filesytem, as well as outputting files into those outside folders.
The best way to understand containers is to open a shell in a container. Remember, containers are self-contained mini operating systems, and the most important thing to understand what they isolate from the rest of the system, and how to get files into and out of the container.
In this section, we talk about testing scripts in a container using `apptainer`. We use `apptainer` (formerly Singularity) in order to run Docker containers on a shared HPC system. This is because Docker itself requires root-level privileges, which is not secure on shared systems.
In order to do our testing, we'll first pull the Docker container, map our bind point (so our container can access files outside of its file system), and then run scripts in the container.
Even if you aren't going to frequently use Apptainer in your work, I recommend trying an interactive shell in a container at least once or twice to learn about the container filesystem and conceptually understand how you connect it to the external filesystem.
### Pulling a Docker Container
Let's pull a docker container from the Docker registry. Note we have to specify `docker://` when we pull the container, because Apptainer has its own internal format called SIF.
```bash
module load Apptainer/1.1.6
apptainer pull docker://biocontainers/samtools:v1.9-4-deb_cv1
```
### Opening a Shell in a Container with `apptainer shell`
When you're getting started, opening a shell using Apptainer can help you test out things like filepaths and how they're accessed in the container. It's hard to get an intuition for how file I/O works with containers until you can see the limited view from the container.
By default, apptainers can see your current directory and navigate to the files in it.
You can open an Apptainer shell in a container using `apptainer shell`. Remember to use `docker://` before the container name. For example:
```bash
module load Apptainer/1.1.6
apptainer shell docker://biocontainers/samtools:v1.9-4-deb_cv1
```
This will load the `apptainer` module, and then open a Bash shell in the container using `apptainer shell`. Once you're in the container, you can test code, especially seeing whether your files can be seen by the container (see @sec-bindpaths). 90% of the issues with using Docker containers has to do with bind paths, so we'll talk about that next.
Once you're in the shell, you can take a look at where `samtools` is installed:
```bash
which samtools
```
Note that the container filesystem is isolated, and we need to explicitly build connections to it (called bind paths) to get files in and out. We'll talk more about this in the next section.
Once we're done testing scripts in our containers, we can exit the shell and get back into the node.
```bash
exit
```
### Using bind paths in containers {#sec-bindpaths}
One thing to keep in mind is that every container has its own filesystem. One of the hardest things to wrap your head around for containers is how their filesystems work, and how to access files that are outside of the container filesystem. We'll call any filesystems outside of the container *external filesystems* to make the discussion a little easier.
By default, the containers have access to your current working directory. We could make this where our scripts live (such as `/home/tladera2/`), but because our data is elsewhere, we'll need to specify that location (`/fh/fast/mylab/`) as well.
The main mechanism we have in Apptainer to access the external filesystem are *bind paths*. Much like mounting a drive, we can bind directories from the external filesystem using these bind paths.
```{mermaid}
flowchart LR
B["External Directory - /fh/fast/mydata/"]
B --read--> C
C --write--> B
A["Container Filesystem - /mydata/"]--write-->C("--bind /fh/fast/mydata/:/mydata/")
C --read--> A
```
I think of bind paths as "tunnels" that give access to particular folders in the external filesystem. Once the tunnel is open, we can access data files, process them, and save them using the bind path.
Say my data lives in `/fh/fast/mydata/`. Then I can specify a bind point called `mydata/` in my `apptainer shell` and `apptainer run` commands so my container can access the files in that directory. Then in the container, we can access the files through the `mydata/` bind path.
We can do this with the `--bind` option:
```bash
apptainer shell --bind /fh/fast/mydata:/mydata docker://biocontainers/samtools:v1.9-4-deb_cv1
```
Note that the bind syntax doesn't have the trailing slash (`/`). That is, note that it is:
```
--bind /fh/fast/mydata: ....
```
Rather than
```
--bind /fh/fast/mydata/: ....
```
Now our `/fh/fast/mydata/` folder will be available as `/mydata/` in my container. We can read and write files to this bind point. For example while in the container's shell, I'd refer to the `.bam` file `/fh/fast/mydata/my_bam_file.bam` as:
```
samtools view -c /mydata/my_bam_file.bam
```
:::{.callout-note}
## Opening a Shell in a Docker Container with Docker
For the most part, due to security reasons, we don't use `docker` on HPC systems. In short, the `docker` group essentially has root-level access to the machine, and it's not a good for security on a shared resource like an HPC.
However, if you have admin level access (for example, on your own laptop), you can open up an interactive shell with `docker run -it`:
```bash
docker run -it biocontainers/samtools:v1.9-4-deb_cv1 /bin/bash
```
This will open a bash shell much like `apptainer shell`. Note that volumes (the docker equivalent of bind paths) are specified differently in Docker compared to Apptainer.
:::
:::{.callout-note}
## WDL makes this way easier
A major point of failure with Apptainer scripting is when our scripts aren't using the right bind paths. It becomes even more complicated when you are running multiple steps.
This is one reason we recommend writing WDL Workflows and a {{<glossary "workflow manager">}} (such as {{<glossary "Cromwell">}} or Sprocket) to run your workflows. You don't have to worry that your bind points are setup correctly, because they are handled by the workflow manager.
:::
### Executing in the Apptainer Shell
Ok, now we have a bind point, so now we can test our script in the shell. For example, we can see if we are invoking `samtools` in the correct way and that our bind points work.
```bash
samtools view -c /mydata/my_bam_file.bam > /mydata/bam_counts.txt
```
Again, trying out scripts in the container is the best way to understand what the container can and can't see.
### Exiting the container when you're done
You can `exit`, like any shell you open. You should be out of the container. Confirm by using `hostname` to make sure you're out of the container.
### Testing outside of the container
Let's take everything that we learned and put it in a script that we can run on the HPC:
```bash
# Script to samtools view -c an input file:
# Usage: ./run_sam.sh <my_bam_file.bam>
# Outputs a count file: my_bam_file.bam.counts.txt
#!/bin/bash
module load Apptainer/1.1.6
apptainer run --bind /fh/fast/mydata:/mydata \
docker://biocontainers/samtools:v1.9-4-deb_cv1 \
samtools view -c /mydata/$1 > /mydata/$1.counts.txt
#apptainer cache clean
module purge
```
We can use this script by the following command:
```
./run_sam.sh chr1.bam
```
And it will output a file called `chr1.bam.counts.txt`.
:::{.callout}
## Apptainer Cache
[The apptainer cache](https://apptainer.org/docs/user/1.0/build_env.html) is where your docker images live. They are translated to the native apptainer `.sif` format.
You can see what's in your cache by using
```
apptainer cache list
```
By default the cache lives at `~/.apptainer/cache`.
If you need to clear out the cache, you can run
```
apptainer cache clean
```
to clear out the cache.
There are a number of environment variables (@sec-environment) that can be set, including login tokens for pulling from a private registry. [More information is here](https://apptainer.org/docs/user/1.0/build_env.html#environment-variables).
:::
### The WILDS Docker Library
The Data Science Lab has a set of Docker containers for common Bioinformatics tasks available in the [WILDS Docker Library](https://hub.docker.com/u/getwilds). These include:
- `bwa mem`
- `samtools`
- `gatk`
- `bcftools`
- `manta`
- `cnvkit`
- `deseq2`
Among many others. Be sure to check it out before you start building your own containers.
## What's Next?
Next week we will discuss using `MiniWDL` (a workflow manager) to process files through multi-step workflows in {{<glossary WDL>}} and run WDL files using PROOF.