def _install_by_version_with_download(cls, version, verbose=False): color_print.debug('switching kernel version with downloading packages') stdout, stderr = verbose_func.verbose_output(verbose) try: debs = cls._fetch_package_list_by_version(version, verbose=verbose) if not debs: return # download necessary *.deb and install temp_cmd = copy.copy(cls.cmd_dpkg_install) version_suffix = None for deb in debs: cls.download_file(deb, config.kernel_packages_dir) filename = deb.split('/')[-1] temp_cmd.append( '{prefix}/{filename}'.format(prefix=config.kernel_packages_dir, filename=filename)) if 'linux-image-' in filename: # get full version for further modification in grub try: version_suffix = re.search( r'linux-image-[a-z]*-?([\d].*?)_', filename).group(1) except AttributeError: # failed to derive complete kernel version pass color_print.debug('installing kernel packages') # installation of kernel may return nonzero, currently ignore them subprocess.run(temp_cmd, stdout=stdout, stderr=stderr, check=False) if version_suffix: color_print.debug('kernel version: %s' % version_suffix) cls._modify_grub(version=version_suffix) else: color_print.warning('failed to derive complete kernel version') color_print.warning('please update grub manually') return True except subprocess.CalledProcessError: return False
def _modify_grub(cls, version=None, recover=False, verbose=False): stdout, stderr = verbose_func.verbose_output(verbose) # edit grub color_print.debug('modifying grub config file') if recover: # recover grub grub_option = '0' else: grub_option = '\"Advanced options for Ubuntu>Ubuntu, with Linux {version}\"'.format( version=version) cmd_modify_grub = 'sed\n-i\ns/^GRUB_DEFAULT=.*$/' \ 'GRUB_DEFAULT={grub_option}/\n/etc/default/grub'.format( grub_option=grub_option).split('\n') subprocess.run( cmd_modify_grub, stdout=stdout, stderr=stderr, check=True) # update grub color_print.debug('updating grub') subprocess.run( cls.cmd_update_grub, stdout=stdout, stderr=stderr, check=True)
def _install_one_gadget_by_version(cls, name, version, mappings=None, verbose=False): stdout, stderr = verbose_func.verbose_output(verbose) # get complete version, e.g. 18.03.1~ce-0~ubuntu complete_version = cls._get_apt_complete_version(name, version, verbose=verbose) if complete_version: color_print.debug( 'installing {gadget} with {version} version'.format( gadget=name, version=complete_version)) # install with the specified version temp_cmd = copy.copy(cls.cmd_apt_install) temp_cmd.append('{name}={version}'.format( name=name, version=complete_version)) try: subprocess.run(temp_cmd, stderr=stderr, stdout=stdout, check=True) except subprocess.CalledProcessError: return False if mappings: mappings[name] = complete_version return True color_print.warning('no candidate version for %s' % name) return False
def _get_kubernetes_cni_version(cls, name, k8s_cni_version, verbose=False): _, stderr = verbose_func.verbose_output(verbose) kubelet_complete_version = cls._get_apt_complete_version( name, k8s_cni_version, verbose=verbose) res = subprocess.run([ 'apt', 'show', '{name}={version}'.format( name=name, version=kubelet_complete_version) ], stdout=subprocess.PIPE, stderr=stderr, check=True) depends = None for entry in res.stdout.decode('utf-8').split('\n'): if entry.startswith('Depends:'): depends = entry break temp_version = None if depends: for depend in depends.split(','): if 'kubernetes-cni' in depend: # maybe regex is better... temp_version = depend.split('(')[-1].split(')')[0].split( ' ')[-1] # OK, regex (thanks to lzx): # temp_version = re.search(r' kubernetes-cni \(= ([\d.]+)\)', depend).group(1) break if temp_version: return cls._get_apt_complete_version('kubernetes-cni', temp_version, verbose=verbose) else: return None
def _add_apt_repository(cls, repo_entry, gpg_url=None, verbose=False): stdout, stderr = verbose_func.verbose_output(verbose) color_print.debug('adding apt repository %s' % repo_entry) try: if gpg_url: cmd_curl_gpg = 'curl -fsSL {gpg_url}'.format( gpg_url=gpg_url).split() res = subprocess.run(cmd_curl_gpg, stdout=subprocess.PIPE, stderr=stderr, check=True) subprocess.run(cls.cmd_apt_add_key, input=res.stdout, stdout=stdout, stderr=stderr, check=True) # add apt repository cmd_apt_add_repository = 'add-apt-repository\n' \ '{repo_entry}'.format( repo_entry=repo_entry).split('\n') subprocess.run(cmd_apt_add_repository, stdout=stdout, stderr=stderr, check=True) return True except subprocess.CalledProcessError: return False
def _install_with_context(cls, gadgets, context=None, verbose=False): stdout, stderr = verbose_func.verbose_output(verbose) worker_template_mappings = dict( ) # used to generate install_k8s_worker_script worker_template_mappings['domestic'] = context.get('domestic', False) cls._pre_configure(verbose=verbose) cls._pre_install(worker_template_mappings, verbose=verbose) # firstly install kubernetes-cni, because of: # issue: https://github.com/kubernetes/kubernetes/issues/75701 # note that the solution below is unstable and ugly to some extent... kubernetes_cni_version = cls._get_kubernetes_cni_version( 'kubelet', gadgets[0]['version'], verbose=verbose) if kubernetes_cni_version: cls._install_one_gadget_by_version('kubernetes-cni', kubernetes_cni_version, worker_template_mappings, verbose=verbose) # install kubelet, kubeadm, kubectl for gadget in gadgets: cls._install_one_gadget_by_version(gadget['name'], gadget['version'], worker_template_mappings, verbose=verbose) # pull necessary k8s images k8s_version = gadgets[0]['version'] images_base, images_extra = cls._get_k8s_images_list_by_version( k8s_version) cls._pull_k8s_images(k8s_version=k8s_version, images_base=images_base, images_extra=images_extra, domestic=context.get('domestic', False), mappings=worker_template_mappings, verbose=verbose) # run kubeadm if not cls._run_kubeadm(k8s_version, context, mappings=worker_template_mappings, verbose=verbose): return False # configure kube config cls._config_auth() # delete master's taint if needed if not context.get('taint_master', None): subprocess.run(cls._cmd_enable_schedule_master, stdout=stdout, stderr=stderr, check=False) # install CNI plugin cls._install_cni_plugin(k8s_version, context, worker_template_mappings, verbose=verbose) # generate install-script for worker cls._update_k8s_worker_script(worker_template_mappings, context, verbose=verbose) return True
def kubernetes_specified_installed(temp_gadget, verbose=False): """Check whether Kubernetes with specified version has been installed. Args: temp_gadget: Kubernetes gadgets (e.g. kubelet, kubeadm). verbose: Verbose or not. Returns: If Kubernetes with specified version has been installed, return True, else False. """ _, stderr = verbose_func.verbose_output(verbose) try: temp_cmd = 'kubectl version'.split() res = subprocess.run( temp_cmd, stdout=subprocess.PIPE, stderr=stderr, check=True) server_string = res.stdout.decode('utf-8').split('\n')[1] server_version = re.search( r'GitVersion:".?([\d]+\.[\d]+\.[\d]+)"', server_string).group(1) if server_version == temp_gadget[0]['version']: return True return False except (FileNotFoundError, AttributeError, IndexError, subprocess.CalledProcessError): return False
def kernel_specified_installed(temp_gadget, verbose=False): """Check whether Linux kernel with specified version has been installed. Args: temp_gadget: Kernel gadgets (e.g. kernel). verbose: Verbose or not. Returns: If kernel with specified version has been installed, return True, else False. """ try: _, stderr = verbose_func.verbose_output(verbose) temp_cmd = 'uname -r'.split() res = subprocess.run( temp_cmd, stdout=subprocess.PIPE, stderr=stderr, check=True) version_string = res.stdout.decode('utf-8') if version_string.startswith(temp_gadget[0]['version']): return True return False except (FileNotFoundError, AttributeError, IndexError, subprocess.CalledProcessError): return False
def containerd_specified_installed(temp_gadgets, verbose=False): """Check whether Containerd with specified version has been installed. Args: temp_gadgets: Docker gadgets (e.g. containerd). verbose: Verbose or not. Returns: If Containerd with specified version has been installed, return True, else False. """ _, stderr = verbose_func.verbose_output(verbose) try: temp_cmd = 'ctr version'.split() res = subprocess.run( temp_cmd, stdout=subprocess.PIPE, stderr=stderr, check=True) server_string = res.stdout.decode('utf-8').split('Server')[1] server_version = re.search( r'Version:\s*(.*)\s+', server_string).group(1) temp_version = _get_gadget_version_from_gadgets( gadgets=temp_gadgets, name='containerd') if temp_version and server_version.startswith(temp_version): return True return False except (FileNotFoundError, AttributeError, IndexError, subprocess.CalledProcessError): return False
def reload_and_restart_docker(verbose=False): """Reload configurations and restart Docker. systemctl daemon-reload && systemctl restart docker Args: verbose: Verbose or not. Returns: Boolean indicating whether configurations is successfully reload and Docker is successfully restarted or not. """ # reload docker daemon configurations if not system_func.reload_daemon_config(verbose=verbose): return False stdout, stderr = verbose_func.verbose_output(verbose) color_print.debug('restarting docker') try: subprocess.run('systemctl restart docker'.split(), stdout=stdout, stderr=stderr, check=True) return True except subprocess.CalledProcessError: color_print.error('failed to restart docker') return False
def _create_k8s_resources(cls, desc_file, verbose=False): stdout, stderr = verbose_func.verbose_output(verbose) temp_cmd = "kubectl create -f".split() temp_cmd.append(desc_file) try: subprocess.run(temp_cmd, stdout=stdout, stderr=stderr, check=True) except subprocess.CalledProcessError: raise
def reboot_system(verbose=False): stdout, stderr = verbose_func.verbose_output(verbose) cmd_reboot = 'init 6'.split() try: subprocess.run(cmd_reboot, stdout=stdout, stderr=stderr, check=True) except subprocess.CalledProcessError: color_print.error('failed to reboot system') return False
def _apt_update(cls, verbose=False): stdout, stderr = verbose_func.verbose_output(verbose) try: subprocess.run(cls.cmd_apt_update, stdout=stdout, stderr=stderr, check=True) return True except subprocess.CalledProcessError: return False
def reload_daemon_config(verbose=False): color_print.debug('reloading daemon configurations') stdout, stderr = verbose_func.verbose_output(verbose) try: subprocess.run('systemctl daemon-reload'.split(), stdout=stdout, stderr=stderr, check=True) return True except subprocess.CalledProcessError: color_print.error('failed to reload daemon configurations') return False
def uninstall(cls, verbose=False): """Uninstall Docker. Args: verbose: Verbose or not. Returns: None. """ stdout, stderr = verbose_func.verbose_output(verbose) subprocess.run(cls.cmd_apt_uninstall + cls._docker_gadgets, stdout=stdout, stderr=stderr, check=False)
def uninstall(cls, verbose=False): """Uninstall Docker. Args: verbose: Verbose or not. Returns: None. """ stdout, stderr = verbose_func.verbose_output(verbose) for gadget in cls._docker_gadgets: temp_cmd = copy.copy(cls.cmd_apt_uninstall) temp_cmd.append(gadget) subprocess.run(temp_cmd, stdout=stdout, stderr=stderr, check=False)
def _pre_install(cls, mappings=None, verbose=False): color_print.debug('pre-installing') stdout, stderr = verbose_func.verbose_output(verbose) # install requirements cls._apt_update(verbose=verbose) subprocess.run(cls.cmd_apt_install + cls._kubernetes_requirements, stdout=stdout, stderr=stderr, check=True) cls._add_apt_repository(gpg_url=config.k8s_apt_repo_gpg, repo_entry=config.k8s_apt_repo_entry, verbose=verbose) # incompatible with ustc repo because it has no gpg currently mappings['gpg_url'] = config.k8s_apt_repo_gpg mappings['repo_entry'] = config.k8s_apt_repo_entry
def _install_by_version_with_apt(cls, version, verbose=False): color_print.debug('switching kernel version with apt') stdout, stderr = verbose_func.verbose_output(verbose) try: # install image package package_name = cls._get_apt_complete_package( 'linux-image', ['linux-image-extra-{version}'.format(version=version), 'generic'], verbose=verbose) color_print.debug('installing kernel package %s' % package_name) version_suffix = package_name.lstrip('linux-image-extra-') temp_cmd = copy.copy(cls.cmd_apt_install) temp_cmd.append(package_name) subprocess.run(temp_cmd, stdout=stdout, stderr=stderr, check=True) cls._modify_grub(version=version_suffix, verbose=verbose) return True except subprocess.CalledProcessError: return False
def _get_kubeadm_token_and_hash(cls, verbose=False): _, stderr = verbose_func.verbose_output(verbose) res = subprocess.run('kubeadm token list'.split(), stdout=subprocess.PIPE, stderr=stderr, check=True) res = res.stdout.decode('utf-8') token = re.search(r'([a-z0-9]{6}\.[a-z0-9]{16})', res).group(1) res = subprocess.run( 'bash {script}'.format(script=config.k8s_hash_generator).split(), stdout=subprocess.PIPE, stderr=stderr, check=True) res = res.stdout.decode('utf-8').strip() ca_cert_hash = res return token, ca_cert_hash
def kata_specified_installed(temp_gadget, kata_runtime_type, verbose=False): """Check whether kata-containers with specified version has been installed. Args: temp_gadget: Kata-containers gadgets (e.g. kata-containers). kata_runtime_type: Runtime of Kata (e.g. qemu/clh/...). verbose: Returns: If kata-containers with specified version has been installed, return True, else False. """ try: _, stderr = verbose_func.verbose_output(verbose) temp_cmd = '{base_dir}/bin/kata-runtime --version'.format( base_dir=config.kata_tar_decompress_dest).split() res = subprocess.run( temp_cmd, stdout=subprocess.PIPE, stderr=stderr, check=True) version_string = res.stdout.decode('utf-8').split('\n')[0] server_version = re.search( r'.*([\d]+\.[\d]+\.[\d]+)', version_string).group(1) if server_version == temp_gadget[0]['version']: # check whether kata runtime type is also correct try: link_target = os.readlink( '{kata_config_dir}/configuration.toml'.format(kata_config_dir=config.kata_config_dir)) actual_runtime_type = link_target.split('.')[0].split('-')[-1] if actual_runtime_type != kata_runtime_type: color_print.warning( 'your expected kata runtime type is {expected}, while current type is {actual}'.format( expected=kata_runtime_type, actual=actual_runtime_type)) color_print.warning( 'you can configure runtime type manually') except (FileNotFoundError, OSError): color_print.warning( 'configuration.toml does not exist or is not an effective symbol link to real configurations') color_print.warning( 'please check configurations in {kata_config_dir} manually'.format( kata_config_dir=config.kata_config_dir)) return True return False except (IndexError, AttributeError, FileNotFoundError, subprocess.CalledProcessError): return False
def _pull_image(cls, image, mappings=None, verbose=False): stdout, stderr = verbose_func.verbose_output(verbose) if mappings: mappings[image] = None if not cls._image_exist(image): color_print.debug('pulling %s' % image) temp_cmd = 'docker pull {image}'.format(image=image).split() try: subprocess.run(temp_cmd, stdout=stdout, stderr=stderr, check=True) return True except subprocess.CalledProcessError: return False else: color_print.debug('%s already pulled' % image)
def _get_apt_complete_version(cls, name, version, verbose=False): _, stderr = verbose_func.verbose_output(verbose) temp_cmd = copy.copy(cls.cmd_apt_madison) temp_cmd.append(name) try: res = subprocess.run(temp_cmd, stdout=subprocess.PIPE, stderr=stderr, check=True) except subprocess.CalledProcessError: return None entries = res.stdout.decode('utf-8').split('\n') complete_version = None for entry in entries: if version in entry: complete_version = entry.split('|')[1].strip() break return complete_version
def _pre_configure(cls, verbose=False): color_print.debug('pre-configuring') stdout, stderr = verbose_func.verbose_output(verbose) # make sure br_netfilter is loaded. subprocess.run(cls._cmd_modprobe, stdout=stdout, stderr=stderr, check=True) # ensure net.bridge.bridge-nf-call-iptables with open('/etc/sysctl.d/k8s.conf', 'a') as f: f.write('net.bridge.bridge-nf-call-ip6tables = 1\n') f.write('net.bridge.bridge-nf-call-iptables = 1\n') # temporarily turn off swap subprocess.run(cls._cmd_swapoff, stdout=stdout, stderr=stderr, check=True)
def _get_apt_complete_package(cls, name, keywords, verbose=False): _, stderr = verbose_func.verbose_output(verbose) temp_cmd = copy.copy(cls.cmd_apt_search) temp_cmd.append(name) try: res = subprocess.run(temp_cmd, stdout=subprocess.PIPE, stderr=stderr, check=True) except subprocess.CalledProcessError: return None entries = res.stdout.decode('utf-8').split('\n') for entry in entries: for keyword in keywords: if keyword not in entry: break else: return entry.split()[0] return None
def _pre_install(cls, verbose=False): stdout, stderr = verbose_func.verbose_output(verbose) # install requirements color_print.debug('installing prerequisites') try: if not cls._apt_update(verbose=verbose): return False subprocess.run(cls.cmd_apt_install + cls._docker_requirements, stdout=stdout, stderr=stderr, check=True) except subprocess.CalledProcessError: return False cls._add_apt_repository(gpg_url=config.docker_apt_repo_gpg, repo_entry=config.docker_apt_repo_entry, verbose=verbose) for repo in config.containerd_apt_repo_entries: cls._add_apt_repository(repo_entry=repo, verbose=verbose) return True
def _act(cls, resources_list, action=None, verbose=False): stdout, stderr = verbose_func.verbose_output(verbose) cmd_kubectl_create = 'kubectl {action} -f'.format( action=action).split() for res in resources_list: color_print.debug('{action}ing {res}'.format( action=action.strip('e'), res=res)) temp_cmd = copy.copy(cmd_kubectl_create) temp_cmd.append(res) try: subprocess.run(temp_cmd, stdout=stdout, stderr=stderr, check=True) except subprocess.CalledProcessError: color_print.error( 'failed to {action} resources in {res}'.format( action=action, res=res)) return False return True
def docker_installed(verbose=False): """Check whether Docker has been installed. Args: verbose: Verbose or not. Returns: If Docker has been installed, return True, else False. """ _, stderr = verbose_func.verbose_output(verbose) try: temp_cmd = 'docker version'.split() subprocess.run( temp_cmd, stdout=subprocess.PIPE, stderr=stderr, check=True) color_print.debug('docker already installed') return True except (FileNotFoundError, AttributeError, IndexError, subprocess.CalledProcessError): return False
def uninstall(cls, verbose=False): """Uninstall Kubernetes. Args: verbose: Verbose or not. Returns: None. """ stdout, stderr = verbose_func.verbose_output(verbose) try: subprocess.run(cls._cmd_kubeadm_reset, input='y\n'.encode('utf-8'), stdout=stdout, stderr=stderr, check=False) except FileNotFoundError: pass subprocess.run(cls.cmd_apt_uninstall + cls._kubernetes_gadgets, stdout=stdout, stderr=stderr, check=False)
def _run_kubeadm(cls, k8s_version, context, mappings=None, verbose=False): color_print.debug('running kubeadm') stdout, stderr = verbose_func.verbose_output(verbose) temp_cmd = 'kubeadm init'.split() temp_cmd.append('--kubernetes-version={k8s_version}'.format( k8s_version=k8s_version)) temp_cmd.append(cls._kubeadm_common_options) if mappings: mappings['kubeadm_options'] = cls._kubeadm_common_options pod_network_cidr = context.get('pod_network_cidr', None) if pod_network_cidr: temp_cmd.append( '--pod-network-cidr={cidr}'.format(cidr=pod_network_cidr)) try: subprocess.run(temp_cmd, stdout=stdout, stderr=stderr, check=True, env=context.get('envs', None)) return True except subprocess.CalledProcessError: color_print.error('failed to run kubeadm') return False
def install_by_version(cls, gadgets, kata_runtime_type, http_proxy=None, https_proxy=None, no_proxy=None, verbose=False): """Install Kata-containers with specified version. Args: gadgets: Kata-containers gadgets (e.g. kata-containers). kata_runtime_type: Runtime of Kata (e.g. qemu/clh/...). http_proxy: HTTP proxy. https_proxy: HTTPS proxy. no_proxy: Domains which should be visited without proxy. verbose: Verbose or not. Returns: Boolean indicating whether Kata-containers is successfully installed or not. """ stdout, stderr = verbose_func.verbose_output(verbose) kata_static_tar_file = config.kata_static_tar_file % gadgets[0]['version'] kata_static_save_path = config.runtime_data_dir + kata_static_tar_file kata_static_tar = Path(kata_static_save_path) # 1. download kata tar if necessary if not kata_static_tar.exists(): color_print.debug( '{kata_tar} is going to be downloaded'.format( kata_tar=kata_static_tar_file)) kata_static_url = ( config.kata_static_url_prefix % gadgets[0]['version']) + kata_static_tar_file proxies = { 'http': http_proxy, 'https': https_proxy, 'no_proxy': no_proxy, } cls.download_file( url=kata_static_url, save_path=kata_static_save_path, proxies=proxies) else: color_print.debug( '{kata_tar} has been downloaded'.format( kata_tar=kata_static_tar_file)) # 2. decompress color_print.debug( 'decompressing files into {dest}'.format( dest=config.kata_tar_decompress_dest)) rmtree(path=config.kata_tar_decompress_dest, ignore_errors=True) system_func.mkdir_if_not_exist(config.kata_tar_decompress_dest) # use --strip-components=3 because `opt/kata/` path from tar are not needed # also, we should not just decompress files into `/` root path # which might cause risks temp_cmd = 'tar xf {file} -C {dest} --strip-components=3'.format( file=kata_static_save_path, dest=config.kata_tar_decompress_dest) try: subprocess.run( temp_cmd.split(), stdout=stdout, stderr=stderr, check=True) except subprocess.CalledProcessError: color_print.error( 'failed to decompress {kata_tar}'.format( kata_tar=kata_static_tar_file)) return False # 3. copy files color_print.debug( 'copying files to {kata_config_dir}'.format( kata_config_dir=config.kata_config_dir)) rmtree(path=config.kata_config_dir, ignore_errors=True) system_func.mkdir_if_not_exist(config.kata_config_dir) for file in glob.glob( config.kata_tar_decompress_dest + 'share/defaults/kata-containers/*'): file_copy( src=file, dst=config.kata_config_dir, follow_symlinks=False) # 4. configure runtime type color_print.debug( 'configuring kata runtime (type: {runtime_type})'.format( runtime_type=kata_runtime_type)) kata_configuration_file = Path( '{kata_config_dir}/configuration.toml'.format(kata_config_dir=config.kata_config_dir)) if kata_configuration_file.exists(): kata_configuration_file.unlink() kata_configuration_file.symlink_to( '{kata_config_dir}/configuration-{runtime_type}.toml'.format( kata_config_dir=config.kata_config_dir, runtime_type=kata_runtime_type)) # [5]. if docker is installed, # modify docker's configuration and restart docker # currently, metarget only supports docker # in the future more CRIs will be supported # see # https://github.com/kata-containers/documentation/blob/master/how-to/run-kata-with-k8s.md color_print.debug('configuring docker with kata-containers') if not cls._configure_docker_with_kata( base_dir=config.kata_tar_decompress_dest): color_print.error( 'failed to configure docker with kata-containers') return False return cls.reload_and_restart_docker(verbose=verbose)