conlink 2.5.2 → 2.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/.nojekyll ADDED
File without changes
@@ -0,0 +1,9 @@
1
+ - **Home**
2
+ - [Quickstart](/)
3
+ - [Usage Notes](/usage-notes.md)
4
+ - **Reference**
5
+ - [Network Configuration Syntax](/reference/network-configuration-syntax.md)
6
+ - **Guides**
7
+ - [Compose Scripts](/guides/compose-scripts.md)
8
+ - [Graphviz Rendering](/guides/graphviz-rendering.md)
9
+ - [Examples](/guides/examples.md)
@@ -0,0 +1,143 @@
1
+ # Compose scripts
2
+
3
+ Conlink also includes scripts that make docker compose a much more
4
+ powerful development and testing environment:
5
+
6
+ * `mdc` - modular management of multiple compose configurations
7
+ * `wait.sh` - wait for network and file conditions before continuing
8
+ * `copy.sh` - recursively copy files with variable templating
9
+
10
+ ## mdc
11
+
12
+ The `mdc` command adds flexibility and power to the builtin overlay
13
+ capability of docker compose. Docker compose can specify multiple
14
+ compose files that will be combined into a single configuration.
15
+ Compose files that are specified later will overlay or override
16
+ earlier compose files. For example, if compose files A and B are
17
+ loaded by docker compose, then the `image` property of a service in
18
+ file B will take precedence (or override) the `image` property for the
19
+ same service in file A. Some properties such as `volumes` and
20
+ `environment` will have the sub-properties merged or appended to.
21
+
22
+ There are several ways that `mdc` adds to the composition capabilities
23
+ of docker compose:
24
+ 1. **Mode/module dependency resolution**. The modes or modules that
25
+ are combined by `mdc` are defined as directories that contain
26
+ mode/module specific content. A `deps` file in a mode/module
27
+ directory is used to specify dependencies on other modes/modules.
28
+ The syntax and resolution algorithm is defined by the
29
+ [resolve-deps](https://github.com/Viasat/resolve-deps) project.
30
+ 2. **Environment variable file combining/overlaying**. Each `.env`
31
+ file that appears in a mode/module directory will be appended into
32
+ a single `.env` file at the top-level where the `mdc` command is
33
+ invoked. Later environment variables will override earlier ones
34
+ with the same name. Variable interpolation and some shell-style
35
+ variable expansion can be used to combine/append environment
36
+ variables. For example if FOO and BAR are defined in an earlier
37
+ mode/module, then BAZ could be defined like this:
38
+ `BAZ="${FOO:-${BAR}-SUFF"` which will set BAZ to FOO if FOO is set,
39
+ otherwise, it will set BAZ to BAR with a "-SUFF" suffix.
40
+ 3. **Directory hierarchy combining/overlaying**. If the mode/module
41
+ directory has subdirectories that themselves contain a "files/"
42
+ sub-directory, then the mode subdirectories will be recursively
43
+ copied into the top-level ".files/" directory. For example,
44
+ consider if the following files exists under the modes "foo" and
45
+ "bar" (with a dependency of "bar" on "foo"):
46
+ `foo/svc1/files/etc/conf1`, `foo/svc2/files/etc/conf2`, and
47
+ `bar/svc1/files/etc/conf1`. When `mdc` is run this will result in
48
+ the following two files: `.files/svc1/etc/conf1` and
49
+ `.files/svc2/etc/conf2`. The content of `conf1` will come from the
50
+ "bar" mode because it is resolved second. The use of the `copy.sh`
51
+ script (described below) simplifies recursive file copying and also
52
+ provides variable templating of copied files.
53
+ 4. **Set environment variables based on the selected modes/modules**.
54
+ When `mdc` is run it will set the following special environment
55
+ variables in the top-level `.env` file:
56
+ * `COMPOSE_FILE`: A colon separated and dependency ordered list of
57
+ compose file paths from each resolved mode/module directory.
58
+ * `COMPOSE_DIR`: The directory where the top-level `.env` is
59
+ created.
60
+ * `COMPOSE_PROFILES`: A comma separated list of each resolved
61
+ mode/module with a `MODE_` prefix on the name. These are docker
62
+ compose profiles that can be used to enable services in one
63
+ mode/module compose file when a different mode/module is
64
+ selected/resolved by `mdc`. For example, if a compose file in
65
+ "bar" has a service that should only be enabled when the "foo"
66
+ mode/module is also requested/resolved, then the service can be
67
+ tagged with the `MODE_foo` profile.
68
+ * `MDC_MODE_[mode]`: Each active mode will have an environment
69
+ variable set to 'enabled'. Important Note: the mode name will
70
+ have all non-alphanumeric characters changed to an underscore so
71
+ that it is usable as the environment variable suffix.
72
+ * `MDC_MODE_DIRS`: A comma separated list of mode/module
73
+ directories. This can be used by other external tools that have
74
+ specific mode/module behavior.
75
+
76
+ Conlink network configuration can be specified in `x-network`
77
+ properties within compose files. This can be a problem with the
78
+ builtin overlay functionality of docker compose because `x-` prefixed
79
+ properties are simply overriden as a whole without any special merging
80
+ behavior. To work around this limitation, conlink has the ability to
81
+ directly merge `x-network` configuration from multiple compose files
82
+ by passing the `COMPOSE_FILE` variable to the conlink `--compose-file`
83
+ parameter (which supports a colon sperated list of compose files).
84
+
85
+ ## wait.sh
86
+
87
+ The dynamic event driven nature of conlink mean that interfaces may
88
+ appear after the container service code starts running (unlike plain
89
+ docker container networking). For this reason, the `wait.sh` script is
90
+ provided to simplify waiting for interfaces to appear (and other
91
+ network conditions). Here is a compose file snippit that will wait for
92
+ `eth0` to appear and for `eni1` to both appear and have an IP address
93
+ assigned before running the startup command (after the `--`):
94
+
95
+ ```
96
+ services:
97
+ svc1:
98
+ volumes:
99
+ - ./conlink/scripts:/scripts:ro
100
+ command: /scripts/wait.sh -i eth0 -I eni1 -- /start-cmd.sh arg1 arg2
101
+ ```
102
+
103
+ In addition to waiting for interfaces and address assignment,
104
+ `wait.sh` can also wait for a file to appear (`-f FILE`), a remote TCP
105
+ port to become accessible (`-t HOST:PORT`), or run a command until it
106
+ completes successfully (`-c COMMAND`).
107
+
108
+
109
+ ## copy.sh
110
+
111
+ One of the features of the `mdc` command is to collect directory
112
+ hierarchies from mode/module directories into a single `.files/`
113
+ directory at the top-level. The intended use of the merged directory
114
+ hierarchy is to be merged into file-systems of running containers.
115
+ However, simple volume mounts will replace entire directory
116
+ hierarchies (and hide all prior files under the mount point). The
117
+ `copy.sh` script is provided for easily merging/overlaying one
118
+ directory hierarchy onto another one. In addition, the `-T` option
119
+ will also replace special `{{VAR}}` tokens in the files being copied
120
+ with the value of the matching environment variable.
121
+
122
+ Here is a compose file snippit that shows the use of `copy.sh` to
123
+ recursively copy/overlay the directory tree in `./.files/svc2` onto
124
+ the container root file-system. In addition, due to the use of the
125
+ `-T` option, the script will replace any occurence of the string
126
+ `{{FOO}}` with the value of the `FOO` environment variable within any
127
+ of the files that are copied:
128
+
129
+ ```
130
+ services:
131
+ svc2:
132
+ environment:
133
+ - FOO=123
134
+ volumes:
135
+ - ./.files/svc2:/files:ro
136
+ command: /scripts/copy.sh -T /files / -- /start-cmd.sh arg1 arg2
137
+ ```
138
+
139
+ Note that instances of `copy.sh` and `wait.sh` can be easily chained
140
+ together like this:
141
+ ```
142
+ /scripts/copy.sh -T /files / -- /scripts/wait.sh -i eth0 -- cmd args
143
+ ```
@@ -0,0 +1,395 @@
1
+ # Examples
2
+
3
+ The [examples](https://github.com/LonoCloud/conlink/tree/master/examples)
4
+ directory contains the necessary files to follow along below.
5
+
6
+ The examples also require a conlink docker image. Build the image for both
7
+ docker and podman like this:
8
+
9
+ ```
10
+ docker build -t conlink .
11
+ podman build -t conlink .
12
+ ```
13
+
14
+ ## test1: compose file with embedded network config
15
+
16
+ Start the test1 compose configuration:
17
+
18
+ ```
19
+ docker-compose -f examples/test1-compose.yaml up --build --force-recreate
20
+ ```
21
+
22
+ From h1 ping the address of h3 (routed via the r0 container):
23
+
24
+ ```
25
+ docker-compose -f examples/test1-compose.yaml exec h1 ping 10.0.0.100
26
+ ```
27
+
28
+
29
+ ## test2: compose file with separate network config and live scaling
30
+
31
+ Start the test2 compose configuration:
32
+
33
+ ```
34
+ docker-compose -f examples/test2-compose.yaml up -d --build --force-recreate
35
+ ```
36
+
37
+ From the first node ping the second:
38
+
39
+ ```
40
+ docker-compose -f examples/test2-compose.yaml exec --index 1 node ping 10.0.1.2
41
+ ```
42
+
43
+ From the second node ping an address in the internet service:
44
+
45
+ ```
46
+ docker-compose -f examples/test2-compose.yaml exec --index 2 node ping 8.8.8.8
47
+ ```
48
+
49
+ Scale the nodes from 2 to 5 and then ping the fifth node from the second:
50
+
51
+ ```
52
+ docker-compose -f examples/test2-compose.yaml up -d --scale node=5
53
+ docker-compose -f examples/test2-compose.yaml exec --index 2 node ping 10.0.1.5
54
+ ```
55
+
56
+
57
+ ## test3: network config file only (no compose) and variable templating
58
+
59
+ ### test3 with docker
60
+
61
+ Start two containers named `ZZZ_node_1` and `ZZZ_node_2`.
62
+
63
+ ```
64
+ docker run --name=ZZZ_node_1 --rm -d --network none alpine sleep 864000
65
+ docker run --name=ZZZ_node_2 --rm -d --network none alpine sleep 864000
66
+ ```
67
+
68
+ Start the conlink container `ZZZ_network` that will setup a network
69
+ configuration that is connected to the other containers:
70
+
71
+ ```
72
+ ./conlink-start.sh -v --mode docker --host-mode docker --network-file examples/test3-network.yaml -- --name ZZZ_network --rm -e NODE_NAME_1=ZZZ_node_1 -e NODE_NAME_2=ZZZ_node_2
73
+ ```
74
+
75
+ In a separate terminal, ping the node 2 from node 1.
76
+
77
+ ```
78
+ docker exec -it ZZZ_node_1 ping 10.0.1.2
79
+ ```
80
+
81
+ ### test3 with rootless podman
82
+
83
+ Same as test3 but using rootless podman instead
84
+
85
+ Start two containers named `ZZZ_node_1` and `ZZZ_node_2`.
86
+
87
+ ```
88
+ podman run --name=ZZZ_node_1 --rm -d --network none alpine sleep 864000
89
+ podman run --name=ZZZ_node_2 --rm -d --network none alpine sleep 864000
90
+ ```
91
+
92
+ Start the conlink container `ZZZ_network` that will setup a network
93
+ configuration that is connected to the other containers:
94
+
95
+ ```
96
+ ./conlink-start.sh -v --mode podman --host-mode podman --network-file examples/test3-network.yaml -- --name ZZZ_network --rm -e NODE_NAME_1=ZZZ_node_1 -e NODE_NAME_2=ZZZ_node_2
97
+ ```
98
+
99
+ In a separate terminal, ping the node 2 from node 1.
100
+
101
+ ```
102
+ podman exec -it ZZZ_node_1 ping 10.0.1.2
103
+ ```
104
+
105
+ ## test4: multiple compose files and container commands
106
+
107
+ Docker-compose has the ability to specify multiple compose files that
108
+ are merged together into a single runtime configuration. This test
109
+ has conlink configuration spread across multiple compose files and
110
+ a separate network config file. The network configuration appears at the
111
+ top-level of the compose files and also within multiple compose
112
+ service definitions.
113
+
114
+ Run docker-compose using two compose files. The first defines the
115
+ conlink/network container and a basic network configuration that
116
+ includes a router and switch (`s0`). The second defines a single
117
+ container (`node1`) and switch (`s1`) that is connected to the router
118
+ defined in the first compose file.
119
+
120
+ ```
121
+ MODES_DIR=./examples/test4-multiple/modes ./mdc node1 up --build --force-recreate
122
+ ```
123
+
124
+ Ping the router host from `node1`:
125
+
126
+ ```
127
+ docker-compose exec node1 ping 10.0.0.100
128
+ ```
129
+
130
+ Restart the compose instance and add another compose file that defines
131
+ two node2 replicas and a switch (`s2`) that is connected to the
132
+ router.
133
+
134
+ ```
135
+ MODES_DIR=./examples/test4-multiple/modes ./mdc node1,nodes2 up --build --force-recreate
136
+ ```
137
+
138
+ From both `node2` replicas, ping `node1` across the switches and `r0` router:
139
+
140
+ ```
141
+ docker-compose exec --index 1 node2 ping 10.1.0.1
142
+ docker-compose exec --index 2 node2 ping 10.1.0.1
143
+ ```
144
+
145
+ From `node1`, ping both `node2` replicas across the switches and `r0` router:
146
+
147
+ ```
148
+ docker-compose exec node1 ping 10.2.0.1
149
+ docker-compose exec node1 ping 10.2.0.2
150
+ ```
151
+
152
+
153
+ Restart the compose instance and add another compose file that starts
154
+ conlink using an addition network file `web-network.yaml`. The network
155
+ file starts up a simple web server on the router.
156
+
157
+ ```
158
+ MODES_DIR=./examples/test4-multiple/modes ./mdc node1,nodes2,web up --build --force-recreate
159
+ ```
160
+
161
+ From the second `node2`, perform a download from the web server running on the
162
+ router host:
163
+
164
+ ```
165
+ docker-compose exec --index 2 node2 wget -O- 10.0.0.100
166
+ ```
167
+
168
+ We can simplify the above launch by using the `all` mode, which contains
169
+ depends on `node1`, `nodes2`, and `web`. Each of those modes depends on
170
+ `base`, so there's no need to specify that again (transitive deps).
171
+
172
+ ```
173
+ MODES_DIR=./examples/test4-multiple/modes ./mdc all up --build --force-recreate
174
+ ```
175
+
176
+ Remove the `.env` file as a final cleanup step:
177
+
178
+ ```
179
+ rm .env
180
+ ```
181
+
182
+
183
+ ## test5: conlink on two hosts with overlay connectivity via geneve
184
+
185
+ Launch a compose instance on host 1 and point it at host 2:
186
+
187
+ ```
188
+ echo "REMOTE=ADDRESS_OF_HOST_2" > .env
189
+ echo "NODE_IP=192.168.100.1" > .env \
190
+ docker-compose --env-file .env -f examples/test5-geneve-compose.yaml up
191
+ ```
192
+
193
+ Launch another compose instance on host 2 and point it at host 1:
194
+ On host 2 run conlink like this:
195
+
196
+ ```
197
+ echo "REMOTE=ADDRESS_OF_HOST_1" > .env
198
+ echo "NODE_IP=192.168.100.2" >> .env \
199
+ docker-compose --env-file .env -f examples/test5-geneve-compose.yaml up
200
+ ```
201
+
202
+ On host 1, start a tcpdump on the main interface capturing Geneve
203
+ (encapsulated) traffic:
204
+
205
+ ```
206
+ sudo tcpdump -nli eth0 port 6081
207
+ ```
208
+
209
+ On host 2, start a ping within the "node1" network namespace created
210
+ by conlink:
211
+
212
+ ```
213
+ docker-compose -f examples/test5-geneve-compose.yaml exec node ping 192.168.100.1
214
+ ```
215
+
216
+ On host 1 you should see bi-directional encapsulated ping traffic on the host.
217
+
218
+
219
+ ## test6: conlink on two hosts deployed with CloudFormation
220
+
221
+ This test uses AWS CloudFormation to deploy two AWS EC2 instances that
222
+ automatically install, configure, and start conlink (and dependencies)
223
+ using the `test5-geneve-compose.yaml` compose file.
224
+
225
+ Authenticate with AWS and set the `MY_KEY`, `MY_VPC`, and `MY_SUBNET`
226
+ variables to refer to a preexisting key pair name, VPC ID, and Subnet
227
+ ID respectively. Then use the AWS CLI to deploy the stack:
228
+
229
+ ```
230
+ export MY_KEY=... MY_VPC=... MY_SUBNET=...
231
+ aws --region us-west-2 cloudformation deploy --stack-name ${USER}-conlink-test6 --template-file examples/test6-cfn.yaml --parameter-overrides KeyPairName=${MY_KEY} VpcId=${MY_VPC} SubnetId=${MY_SUBNET}
232
+ ```
233
+
234
+ The stack will take about 8 minutes to finish deploying. You can
235
+ reduce the time to under a minute if you create your own AMI with the
236
+ pre-signal steps in `BaseUserData` baked in and modify the template to
237
+ use that instead.
238
+
239
+ Once the stack is finish deploying, show the outputs of the stack
240
+ (including instance IP addresses) like this:
241
+
242
+ ```
243
+ aws --region us-west-2 cloudformation describe-stacks --stack-name ${USER}-conlink-test6 | jq '.Stacks[0].Outputs'
244
+ ```
245
+
246
+ Use ssh to connect to instance 1 and 2 (as the "ubuntu" user), then
247
+ sudo to root and cd into `/root/conlink`. You can now run the tcpdump
248
+ and ping test described for test5.
249
+
250
+
251
+ ## test7: MAC, MTU, and NetEm settings
252
+
253
+ This example demonstrates using interface MAC, MTU, and NetEm (tc
254
+ qdisc) settings.
255
+
256
+ Start the test7 compose configuration:
257
+
258
+ ```
259
+ docker-compose -f examples/test7-compose.yaml up --build --force-recreate
260
+ ```
261
+
262
+ Show the links in both node containers to see that on the eth0
263
+ interfaces the MAC addresses are `00:0a:0b:0c:0d:0*` and the MTUs are
264
+ set to `4111`. The eth1 interfaces should have the command line set
265
+ default MTU of `5111`.
266
+
267
+ ```
268
+ docker-compose -f examples/test7-compose.yaml exec --index 1 node ip link
269
+ docker-compose -f examples/test7-compose.yaml exec --index 2 node ip link
270
+ ```
271
+
272
+ Ping the second node from the first to show the the NetEm setting is
273
+ adding 40ms delay in both directions (80ms roundtrip).
274
+
275
+ ```
276
+ docker-compose -f examples/test7-compose.yaml exec --index 1 node ping 10.0.1.2
277
+ ```
278
+
279
+ ## test8: Connections to macvlan/vlan host interfaces
280
+
281
+ This example has two nodes with web servers bound to local addresses.
282
+ The first node is connected to a macvlan sub-interfaces of a host
283
+ physical interface. The second node is connected to a VLAN
284
+ sub-interface of the same host (using VLAN ID/tag 5). Static NAT
285
+ (SNAT+DNAT) is setup inside each container to map the external
286
+ address/interface to the internal address/interface (dummy) where the
287
+ web server is running.
288
+
289
+ Create an environment file with the name of the parent host interface
290
+ and the external IP addresses to assign to each container:
291
+
292
+ ```
293
+ cat << EOF > .env
294
+ HOST_INTERFACE=enp6s0
295
+ NODE1_HOST_ADDRESS=192.168.0.32/24
296
+ NODE2_HOST_ADDRESS=192.168.0.33/24
297
+ EOF
298
+ ```
299
+
300
+ Start the test8 compose configuration using the environment file:
301
+
302
+ ```
303
+ docker-compose --env-file .env -f examples/test8-compose.yaml up --build --force-recreate
304
+ ```
305
+
306
+ Connect to the macvlan node (NODE1_HOST_ADDRESS) from an external host
307
+ on your network (traffic to macvlan interfaces on the same host is
308
+ prevented):
309
+
310
+ ```
311
+ ping -c1 192.168.0.32
312
+ curl 192.168.0.32
313
+ ```
314
+
315
+ Note: to connect to the vlan node (NODE2_HOST_ADDRESS) you will need
316
+ to configure your physical switch/router with routing/connectivity to
317
+ VLAN 5 on the same physical link to your host.
318
+
319
+ ## test9: bridge modes
320
+
321
+ This example demonstrates the supported bridge modes.
322
+
323
+ Start the test9 compose configuration using different bridge modes and
324
+ validate connectivity using ping:
325
+
326
+ ```
327
+ export BRIDGE_MODE="linux" # "ovs", "patch", "auto"
328
+ docker-compose -f examples/test9-compose.yaml up --build --force-recreate
329
+ docker-compose -f examples/test9-compose.yaml exec node1 ping 10.0.1.2
330
+ ```
331
+
332
+ ## test10: port forwarding and routing
333
+
334
+ This example demonstrates port forwarding from the conlink container
335
+ to two containers running simple web servers. It also demonstrates the
336
+ use of a router container and multiple route rules in the other
337
+ containers.
338
+
339
+ Start the test10 compose configuration:
340
+
341
+ ```
342
+ docker-compose -f examples/test10-compose.yaml up --build --force-recreate
343
+ ```
344
+
345
+ Ports 3080 and 8080 are both published on the host by the conlink
346
+ container using standard Docker port mapping. The internal mapping of
347
+ those ports (1080 and 1180 respectively) are both are forwarded to
348
+ port 80 in the node1 container using conlink's port forwarding
349
+ mechanism. The two paths look like this:
350
+
351
+ ```
352
+ host:3080 --> 1080 (in conlink) --> node1:80
353
+ host:8080 --> 1180 (in conlink) --> node1:80
354
+ ```
355
+
356
+ Use curl on the host to query both of these paths to node1:
357
+
358
+ ```
359
+ curl 0.0.0.0:3080
360
+ curl 0.0.0.0:8080
361
+ ```
362
+
363
+ Ports 80 and 81 are published on the host by the conlink container
364
+ using standard Docker port mapping. Then conlink forwards from ports
365
+ 80 and 81 to the first and second replica (respectively) of node2,
366
+ each of which listen internally on port 80. The two paths look like
367
+ this:
368
+
369
+ ```
370
+ host:80 -> 80 (in conlink) -> node2_1:80
371
+ host:81 -> 81 (in conlink) -> node2_2:80
372
+ ```
373
+
374
+ Use curl on the host to query both replicas of node2:
375
+
376
+ ```
377
+ curl 0.0.0.0:80
378
+ curl 0.0.0.0:81
379
+ ```
380
+
381
+ Start a two tcpdump processes in the conlink container to watch
382
+ routed ICMP traffic and then ping between containers across the router
383
+ container:
384
+
385
+ ```
386
+ docker compose -f examples/test10-compose.yaml exec network tcpdump -nli router_1-es1 icmp
387
+ docker compose -f examples/test10-compose.yaml exec network tcpdump -nli router_1-es2 icmp
388
+ ```
389
+
390
+ ```
391
+ docker-compose -f examples/test10-compose.yaml exec node1 ping 10.2.0.1
392
+ docker-compose -f examples/test10-compose.yaml exec node1 ping 10.2.0.2
393
+ docker-compose -f examples/test10-compose.yaml exec node2 ping 10.1.0.1
394
+
395
+ ```
@@ -0,0 +1,32 @@
1
+ # GraphViz network configuration rendering
2
+
3
+ You can use d3 and GraphViz to create a visual graph rendering of
4
+ a network configuration. First start a simple web server in the
5
+ examples directory:
6
+
7
+ ```
8
+ cd examples
9
+ python3 -m http.server 8080
10
+ ```
11
+
12
+ Use the `net2dot` script to transform a network
13
+ configuration into a GraphViz data file (dot language). To render the
14
+ network configuration for example test1, run the following in another
15
+ window:
16
+
17
+ ```
18
+ ./conlink --show-config --compose-file examples/test1-compose.yaml | ./net2dot > examples/test1.dot
19
+ ```
20
+
21
+ Then load `http://localhost:8080?data=test1.dot` in your browser to see the rendered
22
+ image.
23
+
24
+ The file `examples/net2dot.yaml` contains a configuration that
25
+ combines many different configuration elements (veth links, dummy
26
+ interfaces, vlan type links, tunnels, etc).
27
+
28
+ ```
29
+ ./conlink --network-file examples/net2dot.yaml --show-config | ./net2dot > examples/net2dot.dot
30
+ ```
31
+
32
+ Then load `http://localhost:8080?data=net2dot.dot` in your browser.
@@ -0,0 +1,33 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>conlink - Declarative Low-Level Networking for Containers</title>
6
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
7
+ <meta name="description" content="Create (layer 2 and layer 3) networking between containers using a declarative configuration.">
8
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
9
+ <!-- using docsify-themeable Theme: Simple -->
10
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/docsify-themeable@0/dist/css/theme-simple.css">
11
+ </head>
12
+ <body>
13
+ <div id="app"></div>
14
+ <script>
15
+ window.$docsify = {
16
+ name: 'conlink',
17
+ repo: 'LonoCloud/conlink',
18
+ homepage: 'https://raw.githubusercontent.com/LonoCloud/conlink/master/README.md',
19
+ // Uncomment to follow a symlink to root README.md and test rendering locally
20
+ //homepage: '_render-local-README.md',
21
+
22
+ auto2top: true,
23
+ loadSidebar: true,
24
+ subMaxLevel: 2
25
+ }
26
+ </script>
27
+ <!-- Docsify v4 -->
28
+ <script src="//cdn.jsdelivr.net/npm/docsify@4"></script>
29
+ <!-- code highlighting -->
30
+ <script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-bash.min.js"></script>
31
+ <script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-yaml.min.js"></script>
32
+ </body>
33
+ </html>