自動(dòng)化運(yùn)維工具—puppet詳解

轉(zhuǎn)載:
作者:珂兒吖
出處:http://www.cnblogs.com/keerya/
本文版權(quán)歸作者和博客園共有,歡迎轉(zhuǎn)載,但未經(jīng)作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責(zé)任的權(quán)利。
大家寫文都不容易,希望尊重勞動(dòng)成果喲~

一、puppet 介紹

1、puppet是什么

puppet是一個(gè)IT基礎(chǔ)設(shè)施自動(dòng)化管理工具,它能夠幫助系統(tǒng)管理員管理基礎(chǔ)設(shè)施的整個(gè)生命周期: 供應(yīng)(provisioning)、配置(configuration)、聯(lián)動(dòng)(orchestration)及報(bào)告(reporting)。
  基于puppet ,可實(shí)現(xiàn)自動(dòng)化重復(fù)任務(wù)、快速部署關(guān)鍵性應(yīng)用以及在本地或云端完成主動(dòng)管理變更和快速擴(kuò)展架構(gòu)規(guī)模等。
  遵循GPL 協(xié)議(2.7.0-), 基于ruby語言開發(fā)。
  2.7.0 以后使用(Apache 2.0 license)
  對(duì)于系統(tǒng)管理員是抽象的,只依賴于rubyfacter
  能管理多達(dá)40 多種資源,例如:file、user、group、hostpackage、service、cron、execyum repo等。

2、puppet的工作機(jī)制

1)工作模型

puppet 通過聲明性、基于模型的方法進(jìn)行IT自動(dòng)化管理。
  定義:通過puppet 的聲明性配置語言定義基礎(chǔ)設(shè)置配置的目標(biāo)狀態(tài);
  模擬:強(qiáng)制應(yīng)用改變的配置之前先進(jìn)行模擬性應(yīng)用;
  強(qiáng)制:自動(dòng)、強(qiáng)制部署達(dá)成目標(biāo)狀態(tài),糾正任何偏離的配置;
  報(bào)告:報(bào)告當(dāng)下狀態(tài)及目標(biāo)狀態(tài)的不同,以及達(dá)成目標(biāo)狀態(tài)所進(jìn)行的任何強(qiáng)制性改變;
puppet三層模型
  puppet三層模型如下:

puppet三層模型

puppet三層模型

2)工作流程

工作流程

工作流程

3)使用模型

puppet的使用模型分為單機(jī)使用模型master/agent模型,下面我們來看看這兩個(gè)模型的原理圖。
單機(jī)使用模型
  實(shí)現(xiàn)定義多個(gè)manifests --> complier --> catalog --> apply

單機(jī)使用模型工作原理

單機(jī)使用模型工作原理

master/agent模型
  master/agent模型實(shí)現(xiàn)的是集中式管理,即 agent 端周期性向 master 端發(fā)起請(qǐng)求,請(qǐng)求自己需要的數(shù)據(jù)。然后在自己的機(jī)器上運(yùn)行,并將結(jié)果返回給 master 端。
  架構(gòu)和工作原理如下:
架構(gòu)

master/agent模式架構(gòu)

master/agent模式架構(gòu)

工作原理

master/agent模式工作原理

master/agent模式工作原理

3、puppet 名詞解釋

  • 資源:是puppet的核心,通過資源申報(bào),定義在資源清單中。相當(dāng)于ansible中的模塊,只是抽象的更加徹底。
  • 類:一組資源清單。
  • 模塊:包含多個(gè)類。相當(dāng)于ansible中的角色。
  • 站點(diǎn)清單:以主機(jī)為核心,應(yīng)用哪些模塊。

回到頂部

二、puppet 資源詳解

接下來,我們就以單機(jī)模式來具體介紹一下puppet的各個(gè)部分。

1、程序安裝及環(huán)境

首先,我們還是來安裝一下puppet,puppet的安裝可以使用源碼安裝,也可以使用rpm(官方提供)、epel源、官方提供的yum倉庫來安裝(通過下載官方提供的rpm包可以指定官方的yum倉庫)。
  在這里,我們就是用 yum 安裝的方式。

    yum install -y puppet

安裝完成過后,我們可以通過rpm -ql puppet | less來查看一下包中都有一些什么文件。
  其中主配置文件為/etc/puppet/puppet.conf,使用的主程序?yàn)?code>/usr/bin/puppet。

2、puppet 資源簡(jiǎn)介

1)資源抽象

puppet 從以下三個(gè)維度來對(duì)資源完成抽象:

  1. 相似的資源被抽象成同一種資源“類型” ,如程序包資源、用戶資源及服務(wù)資源等;
  2. 將資源屬性或狀態(tài)的描述與其實(shí)現(xiàn)方式剝離開來,如僅說明安裝一個(gè)程序包而不用關(guān)心其具體是通過yum、pkgadd、ports或是其它方式實(shí)現(xiàn);
  3. 僅描述資源的目標(biāo)狀態(tài),也即期望其實(shí)現(xiàn)的結(jié)果,而不是其具體過程,如“確定nginx 運(yùn)行起來” 而不是具體描述為“運(yùn)行nginx命令將其啟動(dòng)起來”;

這三個(gè)也被稱作puppet 的資源抽象層(RAL)
  RAL 由type( 類型) 和provider( 提供者,即不同OS 上的特定實(shí)現(xiàn))組成。

2)資源定義

資源定義通過向資源類型的屬性賦值來實(shí)現(xiàn),可稱為資源類型實(shí)例化;
  定義了資源實(shí)例的文件即清單,manifest;
  定義資源的語法如下:

type {'title':
    attribute1  => value1,
    atrribute2  => value2,
    ……
}

注意:type必須使用小寫字符;title是一個(gè)字符串,在同一類型中必須惟一;每一個(gè)屬性之間需要用“,”隔開,最后一個(gè)“,”可省略。
  例如,可以同時(shí)有名為nginx 的“service”資源和“package”資源,但在“package” 類型的資源中只能有一個(gè)名為“nginx”的資源。

3)資源屬性中的三個(gè)特殊屬性:

  • Namevar:可簡(jiǎn)稱為name;
  • ensure:資源的目標(biāo)狀態(tài);
  • Provider:指明資源的管理接口;

3、常用資源總結(jié)

1)查看資源
  我們可以使用puppet describe來打印有關(guān)Puppet資源類型,提供者和元參數(shù)的幫助。使用語法如下:

puppet describe [-h|--help] [-s|--short] [-p|--providers] [-l|--list] [-m|--meta] [type]
    -l:列出所有資源類型;
    -s:顯示指定類型的簡(jiǎn)要幫助信息;
    -m:顯示指定類型的元參數(shù),一般與-s一同使用;

2)group:管理系統(tǒng)上的用戶組。
  查看使用幫助信息:

enter description here

group使用幫助

屬性:
    name:組名,可以省略,如果省略,將繼承title的值;
    gid:GID;
    system:是否為系統(tǒng)組,true OR false;
    ensure:目標(biāo)狀態(tài),present/absent;
    members:成員用戶;

簡(jiǎn)單舉例如下:

vim group.pp
    group{'mygrp':
        name => 'mygrp',
        ensure => present,
        gid => 2000,
    }

我們可以來運(yùn)行一下:

enter description here

運(yùn)行寫好的group資源

3)user:管理系統(tǒng)上的用戶。
  查看使用幫助信息:

enter description here

user使用幫助

屬性:
    name:用戶名,可以省略,如果省略,將繼承title的值;
    uid: UID;
    gid:基本組ID;
    groups:附加組,不能包含基本組;
    comment:注釋; 
    expiry:過期時(shí)間 ;
    home:用戶的家目錄; 
    shell:默認(rèn)shell類型;
    system:是否為系統(tǒng)用戶 ;
    ensure:present/absent;
    password:加密后的密碼串; 

簡(jiǎn)單舉例如下:

vim user1.pp
    user{'keerr':
        ensure => present,
        system => false,
        comment => 'Test User',
        shell => '/bin/tcsh',
        home => '/data/keerr',
        managehome => true,
        groups => 'mygrp',
        uid => 3000,
    }

4)package:puppet的管理軟件包。
  查看使用幫助信息:

enter description here

package使用幫助

屬性:
    ensure:installed, present, latest, absent, any version string (implies present)
    name:包名,可以省略,如果省略,將繼承title的值;
    source:程序包來源,僅對(duì)不會(huì)自動(dòng)下載相關(guān)程序包的provider有用,例如rpm或dpkg;
    provider:指明安裝方式;

簡(jiǎn)單舉例如下:

vim package1.pp
    package{'nginx':
        ensure  => installed,
        procider    =>  yum
    }   

5)service:定義服務(wù)的狀態(tài)
  查看使用幫助信息:

puppet describe service -s -m

enter description here

service使用幫助

屬性:
    ensure:服務(wù)的目標(biāo)狀態(tài),值有true(running)和false(stopped) 
    enable:是否開機(jī)自動(dòng)啟動(dòng),值有true和false
    name:服務(wù)名稱,可以省略,如果省略,將繼承title的值
    path:服務(wù)腳本路徑,默認(rèn)為/etc/init.d/下
    start:定制啟動(dòng)命令
    stop:定制關(guān)閉命令
    restart:定制重啟命令
    status:定制狀態(tài)

簡(jiǎn)單舉例如下:

vim service1.pp
    service{'nginx':
        ensure  => true,
        enable  => false
    }

6)file:管理文件、目錄、軟鏈接
  查看使用幫助信息:

enter description here

file使用幫助

屬性:
    ensure:目標(biāo)狀態(tài),值有absent,present,file,directory和link
        file:類型為普通文件,其內(nèi)容由content屬性生成或復(fù)制由source屬性指向的文件路徑來創(chuàng)建;
        link:類型為符號(hào)鏈接文件,必須由target屬性指明其鏈接的目標(biāo)文件;
        directory:類型為目錄,可通過source指向的路徑復(fù)制生成,recurse屬性指明是否遞歸復(fù)制;
    path:文件路徑;
    source:源文件;
    content:文件內(nèi)容;
    target:符號(hào)鏈接的目標(biāo)文件; 
    owner:定義文件的屬主;
    group:定義文件的屬組;
    mode:定義文件的權(quán)限;
    atime/ctime/mtime:時(shí)間戳;

簡(jiǎn)單舉例如下:

vim file1.pp
    file{'aaa':
        path    => '/data/aaa',
        source  => '/etc/aaa',
        owner   => 'keerr',
        mode    => '611',
    }

7)exec:執(zhí)行命令,慎用。通常用來執(zhí)行外部命令
  查看使用幫助信息:

puppet describe exec -s -m

enter description here

exec使用幫助

屬性:
    command(namevar):要運(yùn)行的命令;
    cwd:指定運(yùn)行該命令的目錄;
    creates:文件路徑,僅此路徑表示的文件不存在時(shí),command方才執(zhí)行;
    user/group:運(yùn)行命令的用戶身份;
    path:指定命令執(zhí)行的搜索路徑;
    onlyif:此屬性指定一個(gè)命令,此命令正常(退出碼為0)運(yùn)行時(shí),當(dāng)前command才會(huì)運(yùn)行;
    unless:此屬性指定一個(gè)命令,此命令非正常(退出碼為非0)運(yùn)行時(shí),當(dāng)前command才會(huì)運(yùn)行;
    refresh:重新執(zhí)行當(dāng)前command的替代命令;
    refreshonly:僅接收到訂閱的資源的通知時(shí)方才運(yùn)行;

簡(jiǎn)單舉例如下:

vim exec1.pp
    exec{'cmd':
        command => 'mkdir /data/testdir',
        path => ['/bin','/sbin','/usr/bin','/usr/sbin'],
    #   path => '/bin:/sbin:/usr/bin:/usr/sbin',
    }

8)cron:定義周期性任務(wù)
  查看使用幫助信息:

enter description here

cron使用幫助

屬性:
    command:要執(zhí)行的任務(wù)(命令或腳本);
    ensure:目標(biāo)狀態(tài),present/absent;
    hour:時(shí);
    minute:分;
    monthday:日;
    month:月;
    weekday:周;
    user:以哪個(gè)用戶的身份運(yùn)行命令(默認(rèn)為root);
    target:添加為哪個(gè)用戶的任務(wù);
    name:cron job的名稱;

簡(jiǎn)單舉例如下:

vim cron1.pp
    cron{'timesync':
        command => '/usr/sbin/ntpdata 172.16.0.1',
        ensure  => present,
        minute  => '*/3',
        user    => 'root',
    }

我們可以運(yùn)行一下,查看我們的crontab,來看看該任務(wù)是否已經(jīng)被添加:

[root@master manifests]# puppet apply -v --noop cron1.pp        #試運(yùn)行
[root@master manifests]# puppet apply -v  cron1.pp              #運(yùn)行
[root@master manifests]# crontab -l             #查看計(jì)劃任務(wù)
# HEADER: This file was autogenerated at 2017-12-14 15:05:05 +0800 by puppet.
# HEADER: While it can still be managed manually, it is definitely not recommended.
# HEADER: Note particularly that the comments starting with 'Puppet Name' should
# HEADER: not be deleted, as doing so could cause duplicate cron jobs.
# Puppet Name: timesync
*/3 * * * * /usr/sbin/ntpdata 172.16.0.1

9)notify:調(diào)試輸出
  查看使用幫助信息:

<center style="color: rgb(47, 47, 47); font-family: "Helvetica Neue", Helvetica, Tahoma, Arial, "Hiragino Sans GB", STHeiti, "Microsoft YaHei", 微軟雅黑, "WenQuanYi Micro Hei", STXihei, 華文細(xì)黑, Heiti, 黑體, SimSun, 宋體, Song, sans-serif; font-size: 18px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 100; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">
enter description here

</center>

屬性:
    message:記錄的信息
    name:信息名稱

該選項(xiàng)一般用于master/agent模式中,來記錄一些操作的時(shí)間,比如重新安裝了一個(gè)程序呀,或者重啟了應(yīng)用等等。會(huì)直接輸出到代理機(jī)的運(yùn)行日志中。


以上,就是我們常見的8個(gè)資源。其余的資源我們可以使用puppet describe -l來列出,上文中也已經(jīng)說過了~

4、資源的特殊屬性

puppet中也提供了before、requirenotifysubscribe四個(gè)參數(shù)來定義資源之間的依賴關(guān)系和通知關(guān)系。

before:表示需要依賴于某個(gè)資源
require:表示應(yīng)該先執(zhí)行本資源,在執(zhí)行別的資源
notify:A notify B:B依賴于A,且A發(fā)生改變后會(huì)通知B;
subscribe:B subscribe A:B依賴于A,且B監(jiān)控A資源的變化產(chǎn)生的事件;

同時(shí),依賴關(guān)系還可以使用->~>來表示:

-> 表示后資源需要依賴前資源
~> 表示前資源變動(dòng)通知后資源調(diào)用

舉例如下:

vim file.pp
    file{'test.txt':                    #定義一個(gè)文件
        path   => '/data/test.txt',
        ensure  => file,
        source  => '/etc/fstab',
    }

    file{'test.symlink':                #依賴文件建立超鏈接
        path   => '/data/test.symlink',
        ensure  => link,
        target  => '/data/test.txt',
        require => File['test.txt'],
    }

    file{'test.dir':                    #定義一個(gè)目錄
        path   => '/data/test.dir',
        ensure  => directory,
        source  => '/etc/yum.repo.d/',
        recurse => true,
    }

我們還可以使用在最下面統(tǒng)一寫依賴關(guān)系的方式來定義:

vim redis.pp
    package{'reids':
        ensure  => installed,
    }

    file{'/etc/redis.conf':
        source  => '/root/manifets/files/redis.conf',
        ensure  => file,
        owner   => redis,
        group   => root,
        mode    => '0640',
    }

    service{'redis':
        ensure  => running,
        enable  => true,
        hasrestart => true,
    }

    Package['redis'] -> File['/etc/redis.conf'] -> Service['redis'] #定義依賴關(guān)系

tag 標(biāo)簽

如同 anssible 一樣,puppet 也可以定義“標(biāo)簽”——tag,打了標(biāo)簽以后,我們?cè)谶\(yùn)行資源的時(shí)候就可以只運(yùn)行某個(gè)打過標(biāo)簽的部分,而非全部。這樣就更方便于我們的操作。
 一個(gè)資源中,可以有一個(gè)tag也可以有多個(gè)。具體使用語法如下:

type{'title':
    ...
    tag => 'TAG1',
}

type{'title':
    ...
    tag => ['TAG1','TAG2',...],
}

調(diào)用時(shí)的語法如下:

    puppet apply --tags TAG1,TAG2,... FILE.PP

實(shí)例
  首先,我們?nèi)バ薷囊幌?code>redis.pp文件,添加一個(gè)標(biāo)簽進(jìn)去

vim redis.pp
    package{'redis':
        ensure  => installed,
    }

    file{'/etc/redis.conf':
        source  => '/root/manifets/file/redis.conf',
        ensure  => file,
        owner   => redis,
        group   => root,
        mode    => '0640',
        tag    => 'instconf'        #定義標(biāo)簽
    }

    service{'redis':
        ensure  => running,
        enable  => true,
        hasrestart => true,
    }

    Package['redis'] -> File['/etc/redis.conf'] -> Service['redis']

然后,我們手動(dòng)先開啟redis服務(wù):

    systemctl start redis

現(xiàn)在,我們?nèi)バ薷囊幌?code>file目錄下的配置文件:

vim file/redis.conf 
    requirepass keerya

接著,我們就去運(yùn)行redis.pp,我們的配置文件已經(jīng)修改過了,現(xiàn)在想要實(shí)現(xiàn)的就是重啟該服務(wù),實(shí)現(xiàn),需要使用密碼keer登錄:

    puppet apply -v --tags instconf redis.pp

enter description here

redis.pp運(yùn)行結(jié)果

現(xiàn)在,我們就去登錄一下redis看看是否生效:

    redis-cli -a keerya

enter description here

redis驗(yàn)證

驗(yàn)證成功,實(shí)驗(yàn)完成。

5、puppet 變量

puppet 變量以“”開頭,賦值操作符為“=”,語法為`variable_name=value`。
數(shù)據(jù)類型:

字符型:引號(hào)可有可無;但單引號(hào)為強(qiáng)引用,雙引號(hào)為弱引用;支持轉(zhuǎn)義符;
  數(shù)值型:默認(rèn)均識(shí)別為字符串,僅在數(shù)值上下文才以數(shù)值對(duì)待;
  數(shù)組:[]中以逗號(hào)分隔元素列表;
  布爾型值:true, false;不能加引號(hào);
  hash:{}中以逗號(hào)分隔k/v數(shù)據(jù)列表; 鍵為字符型,值為任意puppet支持的類型;{ ‘mon’ => ‘Monday’, ‘tue’ => ‘Tuesday’, };
  undef:從未被聲明的變量的值類型;

正則表達(dá)式:

(?<ENABLED OPTION>:<PATTERN>)
  (?-<DISABLED OPTION>:<PATTERN>)
  OPTIONS:
    i:忽略字符大小寫;
    m:把.當(dāng)換行符;
    x:忽略<PATTERN>中的空白字符;
  (?i-mx:PATTERN)
注意:不能賦值給變量,僅能用在接受=~!~操作符的位置;

1)puppet的變量種類

puppet 種類有三種,為facts,內(nèi)建變量用戶自定義變量
facts:
  由facter提供;top scope;
內(nèi)建變量:
  master端變量
    servername,serverip, serverversion   agent端變量clientcert, clientversion,environment
  parser變量
    $module_name
用戶自定義變量

2)變量的作用域

不同的變量也有其不同的作用域。我們稱之為Scope。
  作用域有三種,top scope,node scope,class scope。
  其生效范圍排序?yàn)椋簍op scope > node scope > class scope

enter description here

變量生效范圍

其優(yōu)先級(jí)排序?yàn)椋簍op scope < node scope < class scope

6、puppet 流程控制語句

puppet 支持if 語句,case 語句selector 語句。

1)if 語句

if語句支持單分支,雙分支和多分支。具體語法如下:

單分支:
    if CONDITION {
        statement
        ……
    }

雙分支:
    if CONDITION {
        statement
        ……
    }
    else{
        statement
        ……      
    }

多分支:
    if CONDITION {
        statement
        ……
    }
    elsif CONDITION{
        statement
        ……
    }
    else{
        statement
        ……      
    }

其中,CONDITION的給定方式有如下三種:

  • 變量
  • 比較表達(dá)式
  • 有返回值的函數(shù)

舉例

vim if.pp
    if $operatingsystemmajrelease == '7' {
        $db_pkg='mariadb-server'
    }else{
        $db_pkg='mysql-server'
    }

    package{"$db_pkg":
        ensure => installed,
    }

2)case 語句

類似 if 語句,case 語句會(huì)從多個(gè)代碼塊中選擇一個(gè)分支執(zhí)行,這跟其它編程語言中的 case 語句功能一致。
  case 語句會(huì)接受一個(gè)控制表達(dá)式和一組 case 代碼塊,并執(zhí)行第一個(gè)匹配到控制表達(dá)式的塊。
  使用語法如下:

case CONTROL_EXPRESSION {
    case1: { ... }
    case2: { ... }
    case3: { ... }
    ……
    default: { ... }
}

其中,CONTROL_EXPRESSION的給定方式有如下三種:

  • 變量
  • 表達(dá)式
  • 有返回值的函數(shù)

各case的給定方式有如下五種:

  • 直接字串;
  • 變量
  • 有返回值的函數(shù)
  • 正則表達(dá)式模式;
  • default

舉例

vim case.pp
    case $osfamily {
        "RedHat": { $webserver='httpd' }
        /(?i-mx:debian)/: { $webserver='apache2' }
        default: { $webserver='httpd' }
    }

    package{"$webserver":
        ensure  => installed,    before  => [ File['httpd.conf'], Service['httpd'] ],
    }

    file{'httpd.conf':
        path    => '/etc/httpd/conf/httpd.conf',
        source  => '/root/manifests/httpd.conf',
        ensure  => file,
    }

    service{'httpd':
        ensure  => running,
        enable  => true,    restart => 'systemctl restart httpd.service',
        subscribe => File['httpd.conf'],
    }

3)selector 語句

Selector 只能用于期望出現(xiàn)直接值(plain value) 的地方,這包括變量賦值、資源屬性、函數(shù)參數(shù)、資源標(biāo)題、其它 selector。
  selector 不能用于一個(gè)已經(jīng)嵌套于于selector 的case 中,也不能用于一個(gè)已經(jīng)嵌套于case 的case 語句中。
  具體語法如下:

CONTROL_VARIABLE ? {
    case1 => value1,
    case2 => value2,
    ...
    default => valueN,
}

其中,CONTROL_EXPRESSION的給定方式有如下三種:

  • 變量
  • 表達(dá)式
  • 有返回值的函數(shù)

各case的給定方式有如下五種:

  • 直接子串;
  • 變量;
  • 有返回值的函數(shù);
  • 正則表達(dá)式模式;
  • default

selectors 使用要點(diǎn):

  1. 整個(gè)selector 語句會(huì)被當(dāng)作一個(gè)單獨(dú)的值,puppet 會(huì)將控制變量按列出的次序與每個(gè)case 進(jìn)行比較,并在遇到一個(gè)匹配的 case 后,將其值作為整個(gè)語句的值進(jìn)行返回,并忽略后面的其它 case。
  2. 控制變量與各 case 比較的方式與 case 語句相同,但如果沒有任何一個(gè) case 與控制變量匹配時(shí),puppet 在編譯時(shí)將會(huì)返回一個(gè)錯(cuò)誤,因此,實(shí)踐中,其必須提供default case。
  3. selector 的控制變量只能是變量或有返回值的函數(shù),切記不能使用表達(dá)式。
  4. 其各 case 可以是直接值(需要加引號(hào)) 、變量、能調(diào)用返回值的函數(shù)、正則表達(dá)式模式或 default。
  5. 但與 case 語句所不同的是,selector 的各 case 不能使用列表。
  6. selector 的各 case 的值可以是一個(gè)除了 hash 以外的直接值、變量、能調(diào)用返回值的函數(shù)或其它的 selector。

舉例

vim selector.pp
    $pkgname = $operatingsystem ? {
        /(?i-mx:(ubuntu|debian))/       => 'apache2',
        /(?i-mx:(redhat|fedora|centos))/        => 'httpd',
        default => 'httpd',
    }
    package{"$pkgname":
        ensure  => installed,
    }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容