Skip to content

Commit 6ee98a3

Browse files
authored
Adds support to run lightweight kubernetes testcontainer using k3s (#313)
* Adds support to run lightweight kubernetes testcontainer using k3s
1 parent 6668ca4 commit 6ee98a3

14 files changed

+379
-104
lines changed

.github/workflows/main.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ jobs:
4343
- rabbitmq
4444
- redis
4545
- selenium
46+
- k3s
4647
runs-on: ${{ matrix.runtime.machine }}
4748
steps:
4849
- uses: actions/checkout@v3

README.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ testcontainers-python facilitates the use of Docker containers for functional an
3535
rabbitmq/README
3636
redis/README
3737
selenium/README
38+
k3s/README
3839

3940
Getting Started
4041
---------------

k3s/README.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.. autoclass:: testcontainers.k3s.K3SContainer

k3s/setup.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from setuptools import setup, find_namespace_packages
2+
3+
description = "K3S component of testcontainers-python."
4+
5+
setup(
6+
name="testcontainers-k3s",
7+
version="0.0.1rc1",
8+
packages=find_namespace_packages(),
9+
description=description,
10+
long_description=description,
11+
long_description_content_type="text/x-rst",
12+
url="https://github.com/testcontainers/testcontainers-python",
13+
install_requires=[
14+
"testcontainers-core",
15+
"kubernetes",
16+
"pyyaml"
17+
],
18+
python_requires=">=3.7",
19+
)

k3s/testcontainers/k3s/__init__.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#
2+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
3+
# not use this file except in compliance with the License. You may obtain
4+
# a copy of the License at
5+
#
6+
# http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
10+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11+
# License for the specific language governing permissions and limitations
12+
# under the License.
13+
14+
from testcontainers.core.config import MAX_TRIES
15+
from testcontainers.core.container import DockerContainer
16+
from testcontainers.core.waiting_utils import wait_for_logs
17+
18+
19+
class K3SContainer(DockerContainer):
20+
"""
21+
K3S container.
22+
23+
Example:
24+
25+
.. doctest::
26+
27+
>>> import yaml
28+
>>> from testcontainers.k3s import K3SContainer
29+
>>> from kubernetes import client, config
30+
31+
>>> with K3SContainer() as k3s:
32+
... config.load_kube_config_from_dict(yaml.safe_load(k3s.config_yaml()))
33+
... pod = client.CoreV1Api().list_pod_for_all_namespaces(limit=1)
34+
... assert len(pod.items) > 0, "Unable to get running nodes from k3s cluster"
35+
"""
36+
37+
KUBE_SECURE_PORT = 6443
38+
RANCHER_WEBHOOK_PORT = 8443
39+
40+
def __init__(self, image="rancher/k3s:latest", **kwargs) -> None:
41+
super(K3SContainer, self).__init__(image, **kwargs)
42+
self.with_exposed_ports(self.KUBE_SECURE_PORT, self.RANCHER_WEBHOOK_PORT)
43+
self.with_env("K3S_URL", f'https://{self.get_container_host_ip()}:{self.KUBE_SECURE_PORT}')
44+
self.with_command("server --disable traefik --tls-san=" + self.get_container_host_ip())
45+
self.with_kwargs(privileged=True, tmpfs={"/run": "", "/var/run": ""})
46+
self.with_volume_mapping("/sys/fs/cgroup", "/sys/fs/cgroup", "rw")
47+
48+
def _connect(self) -> None:
49+
wait_for_logs(self, predicate="Node controller sync successful", timeout=MAX_TRIES)
50+
51+
def start(self) -> "K3SContainer":
52+
super().start()
53+
self._connect()
54+
return self
55+
56+
def config_yaml(self) -> str:
57+
"""This function returns the kubernetes config yaml which can be used
58+
to initialise k8s client
59+
"""
60+
execution = self.get_wrapped_container().exec_run(['cat', '/etc/rancher/k3s/k3s.yaml'])
61+
config_yaml = execution.output.decode('utf-8') \
62+
.replace(f'https://127.0.0.1:{self.KUBE_SECURE_PORT}',
63+
f'https://{self.get_container_host_ip()}:'
64+
f'{self.get_exposed_port(self.KUBE_SECURE_PORT)}')
65+
return config_yaml

k3s/tests/test_k3s.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# The versions below were the current supported versions at time of writing (2022-08-11)
2+
import yaml
3+
from kubernetes import client, config
4+
5+
from testcontainers.k3s import K3SContainer
6+
7+
8+
def test_docker_run_k3s():
9+
with K3SContainer() as k3s:
10+
config.load_kube_config_from_dict(yaml.safe_load(k3s.config_yaml()))
11+
pod = client.CoreV1Api().list_pod_for_all_namespaces(limit=1)
12+
assert len(pod.items) > 0, "Unable to get running nodes from k3s cluster"

requirements.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
-e file:rabbitmq
2121
-e file:redis
2222
-e file:selenium
23+
-e file:k3s
2324
cryptography<37
2425
flake8<3.8.0 # 3.8.0 adds a dependency on importlib-metadata which conflicts with other packages.
2526
pg8000

requirements/macos-latest-3.10.txt

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
# testcontainers-clickhouse
2222
# testcontainers-elasticsearch
2323
# testcontainers-gcp
24+
# testcontainers-k3s
2425
# testcontainers-kafka
2526
# testcontainers-keycloak
2627
# testcontainers-localstack
@@ -40,6 +41,8 @@
4041
# via -r requirements.in
4142
-e file:google
4243
# via -r requirements.in
44+
-e file:k3s
45+
# via -r requirements.in
4346
-e file:kafka
4447
# via -r requirements.in
4548
-e file:keycloak
@@ -90,16 +93,17 @@ azure-storage-blob==12.19.0
9093
# via testcontainers-azurite
9194
babel==2.13.1
9295
# via sphinx
93-
boto3==1.29.1
96+
boto3==1.33.1
9497
# via testcontainers-localstack
95-
botocore==1.32.1
98+
botocore==1.33.1
9699
# via
97100
# boto3
98101
# s3transfer
99102
cachetools==5.3.2
100103
# via google-auth
101-
certifi==2023.7.22
104+
certifi==2023.11.17
102105
# via
106+
# kubernetes
103107
# minio
104108
# opensearch-py
105109
# requests
@@ -137,7 +141,7 @@ ecdsa==0.18.0
137141
# via python-jose
138142
entrypoints==0.3
139143
# via flake8
140-
exceptiongroup==1.1.3
144+
exceptiongroup==1.2.0
141145
# via
142146
# pytest
143147
# trio
@@ -149,7 +153,9 @@ google-api-core[grpc]==2.14.0
149153
# google-api-core
150154
# google-cloud-pubsub
151155
google-auth==2.23.4
152-
# via google-api-core
156+
# via
157+
# google-api-core
158+
# kubernetes
153159
google-cloud-pubsub==2.18.4
154160
# via testcontainers-gcp
155161
googleapis-common-protos[grpc]==1.61.0
@@ -161,20 +167,20 @@ greenlet==3.0.1
161167
# via sqlalchemy
162168
grpc-google-iam-v1==0.12.7
163169
# via google-cloud-pubsub
164-
grpcio==1.59.2
170+
grpcio==1.59.3
165171
# via
166172
# google-api-core
167173
# google-cloud-pubsub
168174
# googleapis-common-protos
169175
# grpc-google-iam-v1
170176
# grpcio-status
171-
grpcio-status==1.59.2
177+
grpcio-status==1.59.3
172178
# via
173179
# google-api-core
174180
# google-cloud-pubsub
175181
h11==0.14.0
176182
# via wsproto
177-
idna==3.4
183+
idna==3.6
178184
# via
179185
# requests
180186
# trio
@@ -201,6 +207,8 @@ kafka-python==2.0.2
201207
# via testcontainers-kafka
202208
keyring==24.3.0
203209
# via twine
210+
kubernetes==28.1.0
211+
# via testcontainers-k3s
204212
markdown-it-py==3.0.0
205213
# via rich
206214
markupsafe==2.1.3
@@ -213,11 +221,15 @@ minio==7.2.0
213221
# via testcontainers-minio
214222
more-itertools==10.1.0
215223
# via jaraco-classes
216-
neo4j==5.14.1
224+
neo4j==5.15.0
217225
# via testcontainers-neo4j
218226
nh3==0.2.14
219227
# via readme-renderer
220-
opensearch-py==2.4.1
228+
oauthlib==3.2.2
229+
# via
230+
# kubernetes
231+
# requests-oauthlib
232+
opensearch-py==2.4.2
221233
# via testcontainers-opensearch
222234
outcome==1.3.0.post0
223235
# via trio
@@ -248,7 +260,7 @@ protobuf==4.25.1
248260
# proto-plus
249261
psycopg2-binary==2.9.9
250262
# via testcontainers-postgres
251-
pyasn1==0.5.0
263+
pyasn1==0.5.1
252264
# via
253265
# pyasn1-modules
254266
# python-jose
@@ -263,7 +275,7 @@ pycryptodome==3.19.0
263275
# via minio
264276
pyflakes==2.1.1
265277
# via flake8
266-
pygments==2.16.1
278+
pygments==2.17.2
267279
# via
268280
# readme-renderer
269281
# rich
@@ -289,6 +301,7 @@ python-arango==7.8.1
289301
python-dateutil==2.8.2
290302
# via
291303
# botocore
304+
# kubernetes
292305
# opensearch-py
293306
# pg8000
294307
python-jose==3.3.0
@@ -299,6 +312,10 @@ pytz==2023.3.post1
299312
# via
300313
# clickhouse-driver
301314
# neo4j
315+
pyyaml==6.0.1
316+
# via
317+
# kubernetes
318+
# testcontainers-k3s
302319
readme-renderer==42.0
303320
# via twine
304321
redis==5.0.1
@@ -308,12 +325,16 @@ requests==2.31.0
308325
# azure-core
309326
# docker
310327
# google-api-core
328+
# kubernetes
311329
# opensearch-py
312330
# python-arango
313331
# python-keycloak
332+
# requests-oauthlib
314333
# requests-toolbelt
315334
# sphinx
316335
# twine
336+
requests-oauthlib==1.3.1
337+
# via kubernetes
317338
requests-toolbelt==1.0.0
318339
# via
319340
# python-arango
@@ -327,7 +348,7 @@ rsa==4.9
327348
# via
328349
# google-auth
329350
# python-jose
330-
s3transfer==0.7.0
351+
s3transfer==0.8.0
331352
# via boto3
332353
scramp==1.4.4
333354
# via pg8000
@@ -338,6 +359,7 @@ six==1.16.0
338359
# azure-core
339360
# ecdsa
340361
# isodate
362+
# kubernetes
341363
# opensearch-py
342364
# python-dateutil
343365
sniffio==1.3.0
@@ -395,6 +417,7 @@ urllib3[socks]==1.26.18
395417
# via
396418
# botocore
397419
# docker
420+
# kubernetes
398421
# minio
399422
# opensearch-py
400423
# python-arango
@@ -403,8 +426,10 @@ urllib3[socks]==1.26.18
403426
# testcontainers-core
404427
# twine
405428
websocket-client==1.6.4
406-
# via docker
407-
wheel==0.41.3
429+
# via
430+
# docker
431+
# kubernetes
432+
wheel==0.42.0
408433
# via -r requirements.in
409434
wrapt==1.16.0
410435
# via testcontainers-core

0 commit comments

Comments
 (0)