前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >几个镜像清理初始化脚本

几个镜像清理初始化脚本

作者头像
后端云
发布2022-11-25 16:43:35
4110
发布2022-11-25 16:43:35
举报
文章被收录于专栏:后端云后端云

virt-sysprep(镜像初始化工具) 参考 virt-sysprep基本使用

有时还要额外定制些镜像初始化工作,需要编写些脚本辅助清理初始化工作

清理 bash-history

代码语言:javascript
复制
#!/usr/bin/env bash## Remove bash history for root and system usersset -o errexitroots_hist="$(find /root -type f -name .bash_history)"users_hist="$(find /home -type f -name .bash_history | tr -s '\n' ' ')"rm -f ${roots_hist} ${users_hist}exit 0

root只有一个,user往往有多个,将换行替换成空格作为rm命令的参数

清理 cloud-init

代码语言:javascript
复制
#!/usr/bin/env bash## Remove all cloud-init run-time data and logs## The removal completely resets cloud-init. When the instance is next# started cloud-init will run all configured modules as if running for the# first timeset -o errexitrm -rf /var/lib/cloud/*rm -f /var/log/cloud-init.logexit 0

清理系统崩溃的dump数据

代码语言:javascript
复制
#!/usr/bin/env bash## Remove crash data generated by kexec-toolsset -o errexitcrash_data_location=(
    "/var/crash/*"
    "/var/log/dump/*")# Include hidden files in globshopt -s nullglob dotglobfor crash_data in ${crash_data_location[@]}do
    rm -rf ${crash_data}doneexit 0

shopt -s nullglob dotglob为了rm -rf *可以包含隐藏文件

清理dhcp-client数据

代码语言:javascript
复制
#!/usr/bin/env bash## Remove DHCP client lease information. Note that Debian 10, and possibly# other OSes, now write a machine specific DUID (DHCP Unique ID) to the# leases fileset -o errexitlease_data_locations=(
    "/var/lib/dhclient/*"
    "/var/lib/dhcp/*")# Include hidden files in globshopt -s nullglob dotglob# Ensure all interfaces are down prior to removing the leases file.# Otherwise the file is recreated by ifdown/dhclientfor iface in $(ls /sys/class/net/ | grep -v lo); do
    # Wait for each interface to be taken down. Timeout after 20 secs
    timer=0
    while grep up "/sys/class/net/${iface}/operstate" &>/dev/null && \
        [[ timer -lt 20 ]]; do
        sleep 1
        let timer=${timer}+1    done

    # If the interface is still up it is likely something has gone wrong
    # with the usual procedures that take the interface down at shutdown.
    # Make a best effort attempt to take the interface down manually
    # temporarily ignoring errors
    if grep up "/sys/class/net/${iface}/operstate" &>/dev/null; then
        set +o errexit        ifdown ${iface}
        set -o errexit    fi

    # Some implementations start the dhcp client when the interface is taken
    # down (even if the interface is statically configured). It is the dhcp
    # client that writes out to the leases file when the interface goes
    # down. Kill the client as a precautionary measure to prevent further
    # interference. Ignore errors in case the dhclient exits between
    # obtaining its pid and killing it
    pid="$(ps aux | grep /sbin/dhclient | grep "${iface}" | tr -s " " | \
        cut -d' ' -f2)"
    if [ "x${pid}" != "x" ]; then
        set +o errexit        kill -9 "${pid}"
        set -o errexit    fidone# Now that all interfaces are down remove all lease filesfor lease_file in ${lease_data_locations[@]}; do
    rm -f ${lease_file}doneexit 0

设定timeout为20秒,等待interface down,若还没down则通过ifdown命令down掉,网口down的时候可能dhcpclient会起进程,kill掉对应网口的dhcpclient进程后进入清理文件的工作。

清理防火墙规则和配置

代码语言:javascript
复制
#!/usr/bin/env bash## Remove any custom firewall rules or firewalld configuration## Modern systems typically make use of the dynamic firewall daemon# firewalld which provides many advantages and additional features over# more traditional approaches. Customisation of the systems firewall rules# it handled through user space tools that output configuration# customisations to /etc/firewalld/zones and /etc/firewalld/services.# Deleting these files will remove any custom configuration from the# system## Older systems or other firewall implementations usually persist rules# information for iptables in /etc/sysconfig/iptables and use the file to# configure the firewall at startup. As such simply deleting the file will# be enough to remove any custom configuration from the systemset -o errexitfw_config_locations=(
    "/etc/sysconfig/iptables"
    "/etc/firewalld/services/*"
    "/etc/firewalld/zones/*")# If using firewalld stop the daemon/service prior to removing the configif command -v systemctl &>/dev/null; then
    if systemctl is-active firewalld.service &>/dev/null; then
        systemctl stop firewalld.service    fifi# Include hidden files in globsshopt -s nullglob dotglob# Remove any custom configurationfor fw_config in ${fw_config_locations[@]}do
    rm -rf ${fw_config}doneexit 0

command -v systemctl检查是否存在systemctl命令。整个脚本先停掉防火墙服务,然后清理相关文件。

清理日志

代码语言:javascript
复制
#!/usr/bin/env bash## Remove log files from the guest## Basic outline for treatment of log directories:# 1. Section 1 of 'log directories' loop:#    Create a tmpfs file system and copy any existing files from the log#    directory to the new file system# 2. Section 2 of 'log directories' loop:#    Mount the tmpfs file system over the top of the existing on-disk log#    files directory. This *hopefully* means than any process relying on#    files in the log directory will still have access to them and will#    allow a clean shutdown while still allowing removal of all on disk#    log files.#    Since tmpfs file systems live on memory the contents copied to them#    will disappear on shutdown# 3. Section 3 of 'log directories' loop:#    Once the tmpfs file system has been mounted the original on-disk log#    directory will no longer be directly accessible. In order to access#    and clear any log files from these disk areas we need to re-mount or#    bind mount the device or file system on which the log directory is#    residing to an alternate location. We can then access and remove#    any files from the disk by doing so from the alternate mount point.## Static log files are removed directly at the end of the script## Original log list taken from Libguestfs's sysprep_operation_logfiles.ml# See https://github.com/libguestfs/libguestfs/tree/master/sysprepset -o errexit# Absolute path to guest log file directories# All files under the given directories will be removedlogd_locations=(
  # Log files and directories
  "/var/log"

  # GDM and session preferences
  "/var/cache/gdm"
  "/var/lib/AccountService/users"

  # Fingerprint service files
  "/var/lib/fprint"

  # fontconfig caches
  "/var/cache/fontconfig"

  # man pages cache
  "/var/cache/man")# Absolute path to static log files that can be removed directlylogf_locations=(
  # Logfiles configured by /etc/logrotate.d/*
  "/var/named/data/named.run"
  # Status file of logrotate
  "/var/lib/logrotate.status"

  # Installation files
  "/root/install.log"
  "/root/install.log.syslog"
  "/root/anaconda-ks.cfg"
  "/root/original-ks.cfg"
  "/root/anaconda-post.log"
  "/root/initial-setup-ks.cfg"

  # Pegasus certificates and other files
  "/etc/Pegasus/*.cnf"
  "/etc/Pegasus/*.crt"
  "/etc/Pegasus/*.csr"
  "/etc/Pegasus/*.pem"
  "/etc/Pegasus/*.srl")# Set mountpoint used to access original on disk contentmntpnt_orig_logd="/mnt/orig_log_dir"# Include hidden files in globshopt -s dotglob# Since the current contents of the log directories will essentially be# copied into memory, we need to ensure that we don't cause an out of# memory condition for the guest. The limit of 128m should be extremely# generous for most systemssum_logd_space=0for logd in ${logd_locations[@]}do
    if [ -d ${logd} ]; then
        logd_space="$(du -sm ${logd} | cut -f1)"
    else
        logd_space=0
    fi
    sum_logd_space=$(( ${sum_logd_space} + ${logd_space} ))
    if [ ${sum_logd_space} -gt 128 ]; then
        echo "ERROR: Space for copying logs into memory > 128mb. Exiting"
        exit 1
    fidone# Test for tmpfs filesystem at /dev/shm creating one if it doesn't exist# If /dev/shm is not present, attempt to create itif ! mount -l -t tmpfs | grep /dev/shm &>/dev/null; then
    [[ -d /dev/shm ]] || mkdir /dev/shm && chmod 1777 /dev/shm    mount -t tmpfs -o defaults,size=128m tmpfs /dev/shmfi# Remove logs from given log directoriesfor logd in ${logd_locations[@]}do
    if [ -d ${logd} ]; then
        # Test if the path or its parents are already on tmpfs
        logd_path="${logd}"
        on_tmpfs=false

        while [[ ${logd_path:0:1} = "/" ]] && [[ ${#logd_path} > 1 ]] && \
              [[ ${on_tmpfs} = false ]]
        do
            defifs=${IFS}
            IFS=$'\n' # Set for convenience with mount output
            for mountpoint in $(mount -l -t tmpfs | cut -d' ' -f3)
            do
                if [ "${mountpoint}" == "${logd_path}" ]; then
                    on_tmpfs=true
                    continue # No need to test further
                fi
            done
            IFS=${defifs} # Restore the default IFS and split behaviour
            logd_path=${logd_path%/*} # Test parent on next iteration
        done

        if [ "${on_tmpfs}" = false ]; then
            # Initialise/reset var used to store where log dir is located
            logd_located_on=""
            # If log directory is a mounted partition we need the device
            defifs=${IFS} && IFS=$'\n' # Set for convenience with df output
            for line in $(df | tr -s ' ')
            do
                # Sixth column of df output is the mountpoint
                if echo ${line} | cut -d' ' -f6 | grep ^${logd}$ &>/dev/null; then
                    # First column of df output is the device
                    logd_located_on="$(echo ${line} | cut -d' ' -f1)"
                fi
            done
            IFS=${defifs} # Restore the default IFS and split behaviour
            # If the log directory is not a mounted partition it must be on
            # the root file system
            [[ "x${logd_located_on}" = "x" ]] && logd_located_on="/"


            # Recreate the log directory under /dev/shm (on tmpfs)
            shmlogd="/dev/shm/${logd}"
            mkdir -p ${shmlogd}
            chmod 1777 ${shmlogd}
            # Copy all files from original log dir to new tmpfs based dir
            files=(${logd}/*) # Array allows wildcard/glob with [[ test ]]
            [[ -e ${files} ]] && cp -pr ${logd}/* ${shmlogd}
            # Replace the original disk based log directory structure with
            # the ephemeral tmpfs based storage by mounting it over the top of
            # the original log directories location on the file system
            mount --bind ${shmlogd} ${logd}


            # Create a mount point from which the contents of the original
            # on-disk log directory can be accessed post mount of the tmpfs
            # file system
            mkdir ${mntpnt_orig_logd}
            # Mount or bind mount in order to access the original on disk logs
            if [ ${logd_located_on} = "/" ]; then
                # Temp file system is a folder on the root file system
                mount_opts="--bind"
                # Contents will be under mount point + original path e.g
                # /mountpoint/var/tmp
                logd_path="${mntpnt_orig_logd}/${logd}"
            else
                # Temp file system is a disk partition
                mount_opts=""
                # Contents will be directly available under the mount point
                logd_path="${mntpnt_orig_logd}"
            fi
            # Mount the device holding the temp file system or bind mount the
            # root file system
            mount ${mount_opts} ${logd_located_on} ${mntpnt_orig_logd}
            # The lastlog file cannot be created on demand for some reason
            # and errors occur if /var/log/lastlog is missing. So, check if
            # '/var/log/lastlog' exists and store the location so we can
            # recreate later
            if [ "${logd}" == "/var/log" ]; then
                lastlog="$(find ${logd_path} -type f -name lastlog)"
            fi
            # Delete all files from the on-disk log directory
            find "${logd_path}" -type f | xargs -I FILE rm -f FILE            # Recreate the /var/log/lastlog file if required
            if [[ "${logd}" == "/var/log" ]] && [[ "x${lastlog}" != "x" ]]; then
                touch "${lastlog}"
            fi
            # Cleanup
            umount ${mntpnt_orig_logd} && rm -rf ${mntpnt_orig_logd}
        fi
    fidone# Remove static log files and files that may be removed directlyfor file in ${logf_locations[@]}do
    [[ -e ${file} ]] && rm -f ${file}doneexit 0

参考 清理临时文件 代码分析

清理machine-id

代码语言:javascript
复制
#!/usr/bin/env bash## Remove the local machine id prevent the possibility of machines having# duplicate identities post cloning operations## The machine id is a identifier first generated at install from a random# source. The id then persists for all subsequent boots and can be used to# uniquely identify the system within the network. The machine-id is often# used in preference to other identifiers such as a mac address, that may# infact change over the lifetime of the machine## For older systems the machine-id is located at /var/lib/dbus/machine-id# and is generated using the dbus-uuid utility.# To trigger the generation of a new machine-id, the machine-id file must# simply be removed. dbus will then create a new file and populate it with# a machine-id string on next boot. Note that if the file is only emptied# (rather than completely removed) then dbus will simply complain about# the fact and will NOT generate a new machine-id.## For more modern systems the machine-id file is located at# /etc/machine-id and /var/lib/dbus/machine-id (if present) is either a# copy of /etc/machine-id or is simply a symlink pointing to it.# Modern systems now use the 'systemd-machine-id-setup' utility to# generate the id file in place of the dbus-uuid tool employed on older# systems.# To trigger the generation of a new machine-id the machine-id file under# /etc must be emptied (NOT removed) and the machine-id file under# /var/lib/dbus (as with older systems) must be removed. If the# /etc/machine-id file is removed rather than emptied the system will not# be able to generate a new machine-id. This has rather dire consequences# for the boot process.# Additionally, if the /etc/machine-id file is emptied but the# /var/lib/dbus/machine-id file remains populated with an id string# then the system will simply copy the dbus machine-id string# into /etc/machine-id on next boot - in other words a new id won't be# created and the old id will be copied back into /etc/machine-idset -o errexit# Machine ID file locationssysd_id="/etc/machine-id"dbus_id="/var/lib/dbus/machine-id"# Remove and recreate (and so empty) the machine-id file under /etcif [ -e ${sysd_id} ]; then
    rm -f ${sysd_id} && touch ${sysd_id}fi# Remove the machine-id file under /var/lib/dbus if it is not a symlinkif [[ -e ${dbus_id} && ! -h ${dbus_id} ]]; then
    rm -f ${dbus_id}fiexit 0

清理邮件

代码语言:javascript
复制
#!/usr/bin/env bash## Remove mail from the local mail spoolset -o errexitmta_list=(
    "exim"
    "postfix"
    "sendmail")mail_spool_locations=(
    "/var/spool/mail/*"
    "/var/mail/*")# Best effort attempt to stop any MTA servicefor mta in ${mta_list[@]}do
    # Systemd
    if command -v systemctl &>/dev/null ; then
        mta_service="$(systemctl list-units --type service | grep ${mta} | \
                       cut -d' ' -f1)"
        if [ "x${mta_service}" != "x" ]; then
            if systemctl is-active ${mta_service} &>/dev/null; then
                systemctl stop ${mta_service}
            fi
        fi
    # Sys-v-init
    else
        mta_service="$(find /etc/init.d/ -iname "*${mta}*")"
        if [ "x${mta_service}" != "x" ]; then
            if ${mta_service} status | grep running &>/dev/null; then
                ${mta_service} stop            fi
        fi
    fidone# Include hidden files in globsshopt -s nullglob dotglob# Remove any mailfor mail_spool in ${mail_spool_locations[@]}do
    rm -rf ${mail_spool}doneexit 0

先停掉的systemd或Sys-v-init管理的邮件服务后再清理相关文件

清理安装包cache

代码语言:javascript
复制
#!/usr/bin/env bash## Remove cache files associated with the guests package managerset -o errexit# Set the locations under which various package managers store cache filescache_locations=(
    # Debian and derivatives
    "/var/cache/apt/"
    # Fedora
    "/var/cache/dnf/"
    # Red Hat and derivatives
    "/var/cache/yum/"
    # SUSE and openSUSE
    "/var/cache/zypp*")# Note that globs in the cache locations will be auto expanded by bashfor cache_dir in ${cache_locations[@]}do
    if [ -d ${cache_dir} ]; then
        # Recursively remove all files from under the given directory
        find ${cache_dir} -type f | xargs -I FILE rm -f FILE    fidoneexit 0

支持Debian,Fedora,centos/rh,suse

清理包管理数据库

代码语言:javascript
复制
#!/usr/bin/env bash## Remove dynamically created package manager files#set -o errexit# RPM Host DB files. RPM will recreate these files automatically if neededrm -f /var/lib/rpm/__db.*# APT lists. APT will recreate these on the first 'apt update'apt_lists=/var/lib/apt/listsif [ -d "${apt_lists}" ]; then
    find "${apt_lists}" -type f | xargs rm -ffiexit 0

清理临时文件

代码语言:javascript
复制
#!/usr/bin/env bash## Remove temporary files from the guest## Basic outline:# 1. Section 1 of 'Main' loop:#    Create a tmpfs file system and copy any existing files from the temp#    directory to the new file system# 2. Section 2 of 'Main' loop:#    Mount the tmpfs file system over the top of the existing on-disk temp#    files directory. This *hopefully* means than any process relying on#    files in the temp directory will still have access to them and will#    allow a clean shutdown while still allowing removal of all on disk#    temp files.#    Since tmpfs file systems live on memory the contents copied to them#    will disappear on shutdown# 3. Section 3 of 'Main' loop:#    Once the tmpfs file system has been mounted the original on-disk temp#    directory will no longer be directly accessible. In order to access#    and clear any temp files from these disk areas we need to re-mount or#    bind mount the device or file system on which the temp directory is#    residing to an alternate location. We can then access and remove#    any files from the disk by doing so from the alternate mount point.set -o errexit# Absolute path to guest temp file directoriestmp_locations=(
    "/tmp"
    "/var/tmp")# Set mountpoint used to access original on disk contentmntpnt_orig_tmp="/mnt/orig_tmp"# Include hidden files in globshopt -s dotglob# Since the current contents of the temp file system will essentially be# copied into memory, we need to ensure that we don't cause an out of# memory condition for the guest. The limit of 128m should be extremely# generous for most systemssum_tmp_space=0for tmp in ${tmp_locations[@]}do
    if [ -d ${tmp} ]; then
        tmp_space="$(du -sm ${tmp} | cut -f1)"
    else
        tmp_space=0
    fi
    sum_tmp_space=$(( ${sum_tmp_space} + ${tmp_space} ))
    if [ ${sum_tmp_space} -gt 128 ]; then
        echo "ERROR: Space for copying tmp into memory > 128mb. Exiting"
        exit 1
    fidone# Test for tmpfs filesystem at /dev/shm creating one if it doesn't exist# If /dev/shm is not present, attempt to create itif ! mount -l -t tmpfs | grep /dev/shm &>/dev/null; then
    [[ -d /dev/shm ]] || mkdir /dev/shm && chmod 1777 /dev/shm    mount -t tmpfs -o defaults,size=128m tmpfs /dev/shmfi# Mainfor tmp in ${tmp_locations[@]}do
    # Test if the path or its parents are already on a tmpfs file system
    tmp_path="${tmp}"
    on_tmpfs=false

    while [[ ${tmp_path:0:1} = "/" ]] && [[ ${#tmp_path} > 1 ]] && \
          [[ ${on_tmpfs} = false ]]
    do
        defifs=${IFS}
        IFS=$'\n' # Set for convenience with mount output
        for mountpoint in $(mount -l -t tmpfs | cut -d' ' -f3)
        do
            if [ "${mountpoint}" == "${tmp_path}" ]; then
                on_tmpfs=true
                continue # No need to test further
            fi
        done
        IFS=${defifs} # Restore the default IFS and split behaviour
        tmp_path=${tmp_path%/*} # Set to test parent on next iteration
    done

    # Perform required operations to delete temp files
    if [ "${on_tmpfs}" = false ]; then
        # Initialise/reset the var used to store where the temp is located
        tmp_located_on=""
        # If the temp directory is a mounted partition we need the device
        defifs=${IFS} && IFS=$'\n' # Set for convenience with df output
        for line in $(df | tr -s ' ')
        do
            # Sixth column of df output is the mountpoint
            if echo ${line} | cut -d' ' -f6 | grep ^${tmp}$ &>/dev/null; then
                # First column of df output is the device
                tmp_located_on="$(echo ${line} | cut -d' ' -f1)"
            fi
        done
        IFS=${defifs} # Restore the default IFS and split behaviour
        # If the temp directory is not a mounted partition it must be on
        # the root file system
        [[ "x${tmp_located_on}" = "x" ]] && tmp_located_on="/"


        # Recreate the temp directory under /dev/shm (on tmpfs)
        shmtmp="/dev/shm/${tmp}"
        mkdir -p ${shmtmp}
        chmod 1777 ${shmtmp}
        # Copy all files from original temp dir to new tmpfs based dir
        files=(${tmp}/*) # Array allows wildcard/glob with [[ test ]]
        [[ -e ${files} ]] && cp -pr ${tmp}/* ${shmtmp}
        # Replace the original disk based temp directory structure with
        # the ephemeral tmpfs based storage by mounting it over the top of
        # the original temp directories location on the file system
        mount --bind ${shmtmp} ${tmp}


        # Create a mount point from which the contents of the original
        # on-disk temp directory can be accessed post mount of the tmpfs
        # file system
        mkdir ${mntpnt_orig_tmp}
        # Mount or bind mount in order to access the original on disk temp
        if [ ${tmp_located_on} = "/" ]; then
            # Temp file system is a folder on the root file system
            mount_opts="--bind"
            # Contents will be under mount point + original path e.g
            # /mountpoint/var/tmp
            tmp_path="${mntpnt_orig_tmp}/${tmp}"
        else
            # Temp file system is a disk partition
            mount_opts=""
            # Contents will be directly available under the mount point
            tmp_path="${mntpnt_orig_tmp}"
        fi
        # Mount the device holding the temp file system or bind mount the
        # root file system
        mount ${mount_opts} ${tmp_located_on} ${mntpnt_orig_tmp}
        # Delete all files from the on-disk temp directory
        files=(${tmp_path}/*)
        [[ -e ${files} ]] && rm -rf ${tmp_path}/*        # Cleanup
        umount ${mntpnt_orig_tmp} && rm -rf ${mntpnt_orig_tmp}
    fidoneexit 0

‘Main’循环分三部分:

  1. 创建一个tmpfs文件系统,将要清理的文件夹复制到tmpfs文件系统
  2. 上一步意味着,若有进程需要读写要清理的文件,不受影响,只是实际操作的位置变成了tmpfs文件系统。tmpfs 文件系统存在于内存中,关机后数据会消失。
  3. 清理要清理的文件夹。因为原来的目录已成为tmpfs的挂载点,原来的目录已经不可访问,所以为了删除原来目录的文件需要将原来的目录再次挂载到一个新的挂载点。对应代码中的${mntpnt_orig_tmp}

mount tmpfs -> 最初要清理的目录 mount 最初要清理的目录 -> ${mntpnt_orig_tmp} 有上面两次挂载,前者只发生一次,关机后tmpfs自动消失。后者每一个while循环发生一次,因为只要清理完数据,就可以umount了。目的不一样,后者是为了清理数据,前者为了不影响需要操作清理对象的进程

代码用了几个技巧:

  • du -sm 获取文件夹的大小,单位MB,统计对象不超过128MB,防止发生内存溢出。
  • 用变量defifs临时保存了IFS
  • tmp_path=${tmp_path%/*}可以获取其父目录继续下一次迭代。
  • mountmount --bind,分设备和文件夹两种情况。

清理yum manager uuid

代码语言:javascript
复制
#!/usr/bin/env bash## Remove the yum package manager UUID associated with the guest## A new UUID will be automatically generated the next time yum is runset -o errexituuid="/var/lib/yum/uuid"[[ -e ${uuid} ]] && rm -f ${uuid}exit 0
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-07-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 后端云 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 清理 bash-history
  • 清理 cloud-init
  • 清理系统崩溃的dump数据
  • 清理dhcp-client数据
  • 清理防火墙规则和配置
  • 清理日志
  • 清理machine-id
  • 清理邮件
  • 清理安装包cache
  • 清理包管理数据库
  • 清理临时文件
  • 清理yum manager uuid
相关产品与服务
腾讯云代码分析
腾讯云代码分析(内部代号CodeDog)是集众多代码分析工具的云原生、分布式、高性能的代码综合分析跟踪管理平台,其主要功能是持续跟踪分析代码,观测项目代码质量,支撑团队传承代码文化。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档