-
Notifications
You must be signed in to change notification settings - Fork 50
Expand file tree
/
Copy pathinstall.py
More file actions
291 lines (239 loc) · 10.3 KB
/
install.py
File metadata and controls
291 lines (239 loc) · 10.3 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
# INTEL CONFIDENTIAL
#
# Copyright (C) 2022 Intel Corporation
#
# This software and the related documents are Intel copyrighted materials, and your use of them is governed by
# the express license under which they were provided to you ("License"). Unless the License provides otherwise,
# you may not use, modify, copy, publish, distribute, disclose or transmit this software or the related documents
# without Intel's prior written permission.
#
# This software and the related documents are provided as is, with no express or implied warranties,
# other than those that are expressly stated in the License.
"""
A module responsible for K3S installation.
"""
import gzip
import logging
import os
import re
import shutil
import stat
import subprocess
import tempfile
import time
from typing import IO
from urllib.parse import urlparse
import requests
import yaml
from cli_utils.platform_logs import subprocess_run
from constants.paths import (
CONFIG_TOML_TMPL_PATH,
K3S_AUDIT_LOG_PATH,
K3S_IMAGES_DIR_PATH,
K3S_INSTALL_LOG_FILE_PATH,
K3S_INSTALLATION_MARK_FILEPATH,
K3S_KUBECONFIG_PATH,
K3S_OFFLINE_INSTALLATION_FILES_PATH,
K3S_REGISTRIES_FILE_PATH,
K3S_REMOTE_KUBECONFIG_PATH,
K3S_SELINUX_OFFLINE_INSTALLATION_FILES_PATH,
USR_LOCAL_BIN_PATH,
)
from constants.platform import EXTERNAL_REGISTRY_ADDRESS, PLATFORM_NAMESPACE
from k3s.config import k3s_configuration
from k3s.detect_ip import get_first_public_ip
from k3s.detect_selinux import is_selinux_installed
from platform_utils.install_system_packages import install_packages_with_dnf
logger = logging.getLogger(__name__)
class K3SInstallationError(Exception):
"""
Raised when K3S installation script fails
"""
def _download_script(target_file: IO[bytes]):
"""
Download K3S script to 'target_file' and make script executable.
"""
script_addr = "https://get.k3s.io"
logger.debug(f"Downloading K3S installation script from {script_addr}.")
response = requests.get(script_addr) # noqa: S113
target_file.write(response.content)
target_file_stat = os.stat(target_file.name)
os.chmod(target_file.name, target_file_stat.st_mode | stat.S_IEXEC)
def _set_local_registry(external_registry_address: str):
"""
Configures local docker registry - as a replacement for docker.io registry to avoid docker pull limit
Function called only when the EXTERNAL_REGISTRY_ADDRESS env variable is set.
:param external_registry_address: Address of the external registry to be used as a replacement for docker.io
"""
if not external_registry_address.startswith("http"):
external_registry_address = "https://" + external_registry_address
parsed_url = urlparse(external_registry_address)
content = f"""mirrors:
docker.io:
endpoint:
- "{parsed_url.scheme}://{parsed_url.netloc}" """
if parsed_url.path.strip('/'):
content += f"""
rewrite:
"(.*)": "{parsed_url.path.strip('/')}/$1" """
content += f"""
configs:
"{parsed_url.scheme}://{parsed_url.netloc}":
tls:
insecure_skip_verify: true """
os.makedirs(os.path.dirname(K3S_REGISTRIES_FILE_PATH), exist_ok=True)
try:
with open(K3S_REGISTRIES_FILE_PATH, "w", encoding="utf-8") as file:
file.write(content)
except (OSError) as error:
logger.exception(f"Error occurred while setting up local registry: {str(error)}")
def _update_containerd_config(logs_file_path: str):
"""
Update containerd config.toml.tmpl to point to 'current' location.
"""
if not os.path.isfile(CONFIG_TOML_TMPL_PATH):
logger.debug(f"Config file: '{CONFIG_TOML_TMPL_PATH}' not found, skipping.")
return
with open(CONFIG_TOML_TMPL_PATH) as config_toml_tmpl:
content = config_toml_tmpl.read()
# Replace k3s hashes with 'current' location link
new_content = re.sub(r"[a-fA-F0-9]{64}", "current", content)
# Skip if 'config.toml.tmpl' already uses 'current' location link
if content == new_content:
return
try:
with open(CONFIG_TOML_TMPL_PATH, "w") as config_toml_tmpl:
config_toml_tmpl.write(new_content)
logger.debug("'config.toml.tmpl' file updated successfully.")
except (OSError, PermissionError) as error:
logger.exception(f"Error occurred while updating the 'config.toml.tmpl' file: {str(error)}")
raise K3SInstallationError from error
with open(logs_file_path, "a", encoding="utf-8") as log_file:
subprocess_run(command=["systemctl", "restart", "k3s"], log_file=log_file, env=os.environ.copy())
def _k3s_ready(time_wait: int = 300, delay: int = 10) -> bool:
"""
Wait default 5 minutes for k3s node readiness
"""
env = os.environ.copy()
node_cmd = ["k3s", "kubectl", "get", "nodes"]
for _ in range(0, time_wait, delay):
try:
result = subprocess.run( # noqa: S603
node_cmd,
capture_output=True,
text=True,
timeout=5,
check=False,
env=env,
)
if "Ready" in result.stdout:
return True
except (subprocess.CalledProcessError, subprocess.TimeoutExpired):
pass
time.sleep(delay)
return False
def _run_installer(k3s_script_path: str, logs_file_path: str):
"""
Run K3S installer by running downloaded script in 'k3s_script_path' and writing
execution logs to 'logs_dir'.
"""
k3s_env_vars = k3s_configuration.to_env_var_dict()
env = os.environ.copy()
env.update(k3s_env_vars)
os.makedirs(os.path.dirname(K3S_AUDIT_LOG_PATH), exist_ok=True)
logger.debug(
f"Running K3s installer with the configuration:\nINSTALL_K3S_VERSION: "
f"{k3s_env_vars['INSTALL_K3S_VERSION']}, INSTALL_K3S_EXEC: {k3s_env_vars['INSTALL_K3S_EXEC']}."
)
with open(logs_file_path, "a", encoding="utf-8") as log_file:
subprocess_run(k3s_script_path, log_file, env=env)
if not _k3s_ready():
raise K3SInstallationError("K3S failed to become ready within expected timeout")
def _mark_k3s_installation():
"""Creates a stub file in /etc/rancher/k3s directory to differentiate platform installer k3s installation"""
with open(K3S_INSTALLATION_MARK_FILEPATH, "w") as stub_file:
stub_file.write("")
logger.debug(
f"Created a stub file {K3S_INSTALLATION_MARK_FILEPATH} to differentiate platform installer "
f"k3s installation."
)
def _adjust_k3s_kubeconfig_server_address():
"""
Make a copy of K3s installer kubeconfig and change server address in this copy to provided external address or
first non-local IPv4 address found on local host.
"""
logger.debug("Adjusting K3s kubeconfig server address.")
first_nonlocal_host_ip = get_first_public_ip()
if first_nonlocal_host_ip is None:
logger.warning("Couldn't find any nonlocal host ip.")
return
external_address = first_nonlocal_host_ip
shutil.copy2(K3S_KUBECONFIG_PATH, K3S_REMOTE_KUBECONFIG_PATH)
logger.debug(f"Copied {K3S_KUBECONFIG_PATH} to {K3S_REMOTE_KUBECONFIG_PATH}.")
with open(K3S_REMOTE_KUBECONFIG_PATH) as kubeconfig_file:
kubeconfig = yaml.safe_load(kubeconfig_file)
kubeconfig["clusters"][0]["cluster"]["server"] = f"https://{external_address}:6443"
with open(K3S_REMOTE_KUBECONFIG_PATH, mode="w") as kubeconfig_file:
yaml.dump(kubeconfig, kubeconfig_file)
logger.debug(f"Server address in {K3S_REMOTE_KUBECONFIG_PATH} is set: {external_address}.")
def _set_default_namespace():
"""
Set default namespace inside kubeconfig files to value defined inside PLATFORM_NAMESPACE constant
"""
with open(K3S_REMOTE_KUBECONFIG_PATH) as kubeconfig_remote_file:
kubeconfig_remote = yaml.safe_load(kubeconfig_remote_file)
kubeconfig_remote["contexts"][0]["context"]["namespace"] = PLATFORM_NAMESPACE
with open(K3S_REMOTE_KUBECONFIG_PATH, mode="w") as kubeconfig_remote_file:
yaml.safe_dump(kubeconfig_remote, kubeconfig_remote_file)
logger.debug(f"Namespace in {K3S_REMOTE_KUBECONFIG_PATH} is set to: {PLATFORM_NAMESPACE}.")
def _prepare_k3s_files_structure():
"""
Prepare k3s files structure. Place binary and images in proper locations.
"""
# Copy k3s binary
shutil.copy2(f"{K3S_OFFLINE_INSTALLATION_FILES_PATH}/k3s", USR_LOCAL_BIN_PATH)
# Copy k3s images
os.makedirs(K3S_IMAGES_DIR_PATH, exist_ok=True)
with (
gzip.open(f"{K3S_OFFLINE_INSTALLATION_FILES_PATH}/k3s-airgap-images-amd64.tar.gz", "rb") as f_in,
open(f"{K3S_IMAGES_DIR_PATH}/k3s-airgap-images-amd64.tar", "wb") as f_out,
):
shutil.copyfileobj(f_in, f_out)
# Set executable permissions
if is_selinux_installed():
with open(K3S_INSTALL_LOG_FILE_PATH, "a", encoding="utf-8") as log_file:
subprocess_run(["chcon", "-t", "bin_t", f"{USR_LOCAL_BIN_PATH}/k3s"], log_file)
os.chmod(f"{USR_LOCAL_BIN_PATH}/k3s", stat.S_IXUSR | stat.S_IRUSR | stat.S_IWUSR)
os.chmod(f"{K3S_OFFLINE_INSTALLATION_FILES_PATH}/install.sh", stat.S_IXUSR | stat.S_IRUSR | stat.S_IWUSR)
def _install_k3s_selinux_rpm() -> None:
if is_selinux_installed():
install_packages_with_dnf(
packages_path=K3S_SELINUX_OFFLINE_INSTALLATION_FILES_PATH,
log_file_path=K3S_INSTALL_LOG_FILE_PATH,
disable_repos=True,
)
def install_k3s( # noqa: ANN201
logs_file_path: str = K3S_INSTALL_LOG_FILE_PATH,
setup_remote_kubeconfig: bool = True,
):
"""
Install K3S to current system. Write installation logs to 'logs_dir'. Use optionally 'external_address' to adjust
produced kubeconfig.
"""
with tempfile.NamedTemporaryFile(delete=False) as tmp:
try:
if EXTERNAL_REGISTRY_ADDRESS:
_set_local_registry(EXTERNAL_REGISTRY_ADDRESS)
_download_script(tmp)
tmp.close()
_install_k3s_selinux_rpm()
_run_installer(k3s_script_path=tmp.name, logs_file_path=logs_file_path)
_update_containerd_config(logs_file_path=logs_file_path)
_mark_k3s_installation()
except subprocess.CalledProcessError as ex:
raise K3SInstallationError from ex
finally:
os.remove(tmp.name)
if setup_remote_kubeconfig:
_adjust_k3s_kubeconfig_server_address()
_set_default_namespace()