8. 隔壁小孩被程式設計師欺負,我用了一篇 K8s的ConfigMap讓他心服口服!
文章目錄
文章目錄
在K8S中,容器本身是非持久化的,當容器崩潰後,kubelet將以映象的初始狀態重新啟動容器,但是此時之前容器的資料已經丟失,我們該如何保護好容器的資料呢?
在同一Pod中的容器往往需要共享一些資料,此時我們又該如何實現呢?
這個時候就需要儲存來解決這兩個問題。
一、ConfigMap
ConfigMap
功能在 Kubernetes1.2
版本中引入,許多應用程式會從配置檔案、命令列引數或環境變數中讀取配置資訊。 ConfigMap API
給我們提供了向容器中注入配置資訊的機制, ConfigMap
可以被用來儲存單個屬性,也可以用來儲存整個配置檔案或者 JSON
二進位制大物件。
1.1 ConfigMap 的建立
① 使用目錄建立
[root@master configmap]
apple=8.0
orange=3.5
[root@master configmap]
beef=55.0
pork=28.0
[root@master configmap]
configmap/shop-config created
[root@master configmap]
NAME DATA AGE
shop-config 2 8s
[root@master configmap]
[root@master configmap]
② 使用檔案建立
[root@master configmap]
tea=3.0
coffee=4.0
[root@master configmap]
configmap/drink-config created
[root@master configmap]
③ 使用字面值建立
使用文字值建立,利用 --from-literal
引數傳遞配置資訊,該引數可以使用多次,格式如下:
[root@master configmap]
configmap/snacks-config created
[root@master configmap]
1.2 使用 ConfigMap
使用 ConfigMap
有三種方式,一種是通過環境變數的方式,直接傳遞 pod
,另一種是通過在 pod
的命令列下執行的方式,第三種是使用 volume
的方式掛載入到 pod
內。
① 使用 ConfigMap 來替代環境變數
[root@master configmap]
apiVersion: v1
kind: ConfigMap
metadata:
name: animal-config
namespace: default
data:
cat: cute
dog: lovely
[root@master configmap]
configmap/animal-config created
[root@master configmap]
apiVersion: v1
kind: Pod
metadata:
name: use-configmap-pod
spec:
containers:
- name: test-container
image: hub.hc.com/library/myapp:v1
command: [ "/bin/sh", "-c", "env" ]
env:
- name: animal-config-cat
valueFrom:
configMapKeyRef:
name: animal-config
key: cat
- name: animal-config-coffee
valueFrom:
configMapKeyRef:
name: animal-config
key: dog
envFrom:
- configMapRef:
name: snacks-config
restartPolicy: Never
[root@master configmap]
pod/use-configmap-pod created
[root@master configmap]
② 用 ConfigMap 設定命令列引數
apiVersion: v1
kind: Pod
metadata:
name: use--configmap-pod2
spec:
containers:
- name: test-container
image: hub.hc.com/library/myapp:v1
command: [ "/bin/sh", "-c", "echo $(animal-config-cat) $(animal-config-dog)" ]
env:
- name: animal-config-cat
valueFrom:
configMapKeyRef:
name: animal-config
key: cat
- name: animal-config-dog
valueFrom:
configMapKeyRef:
name: animal-config
key: dog
restartPolicy: Never
③ 通過資料卷外掛使用 ConfigMap
在資料卷裡面使用這個 ConfigMap
,有不同的選項。最基本的就是將檔案填入資料卷,在這個檔案中,鍵就是檔名,鍵值就是檔案內容。
apiVersion: v1
kind: Pod
metadata:
name: use-configmap-pod3
spec:
containers:
- name: test-container
image: hub.hc.com/library/myapp:v1
command: [ "/bin/sh", "-c", "ls /etc/config" ]
volumeMounts:
- name: config-volume
mountPath: /etc/config
volumes:
- name: config-volume
configMap:
name: animal-config
restartPolicy: Never
1.3 ConfigMap 熱更新
[root@master configmap]
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: my-nginx
spec:
replicas: 1
template:
metadata:
labels:
run: my-nginx
spec:
containers:
- name: my-nginx
image: hub.hc.com/library/myapp:v1
ports:
- containerPort: 80
volumeMounts:
- name: config-volume
mountPath: /etc/config
volumes:
- name: config-volume
configMap:
name: snacks-config
[root@master configmap]
deployment.extensions/my-nginx created
[root@master configmap]
NAME READY STATUS RESTARTS AGE
my-nginx-bc7499bd8-hrgl9 1/1 Running 0 2s
[root@master configmap]
6.9
[root@master configmap]
[root@master configmap]
8.8
二、Secret
Secret 解決了密碼、token、金鑰等敏感資料的配置問題,而不需要把這些敏感資料暴露到映象或者 Pod Spec中。Secret 可以以 Volume 或者環境變數的方式使用
Secret
有三種型別:
- Service Account:用來訪問
Kubernetes API
,由Kubernetes
自動建立,並且會自動掛載到Pod
的/run/secrets/kubernetes.io/serviceaccount
目錄中 - Opaque:
base64
編碼格式的Secert
,用來儲存密碼、金鑰等 - kubernetes.io/dockerconfigjson:用來儲存私有
docker registry
的認證資訊
2.1 Service Account
[root@master ~]
ca.crt namespace token
2.2 Opaque
Opaque
型別的資料是一個 map
型別,要求 value
是 base64
編碼格式:
[root@master ~]
YWRtaW4=
[root@master ~]
MWYyZDFlMmU2N2Rm
建立 Opaque
型別的 secret
:
[root@master secert]
apiVersion: v1
kind: Secret
metadata:
name: mysecret
type: Opaque
data:
username: YWRtaW4=
password: MWYyZDFlMmU2N2Rm
[root@master secert]
secret/mysecret created
[root@master secert]
NAME TYPE DATA AGE
mysecret Opaque 2 93s
將 Secret
掛載到 Volume
中:
[root@master secert]
apiVersion: v1
kind: Pod
metadata:
labels:
name: secret-test
name: secret-test
spec:
volumes:
- name: secrets
secret:
secretName: mysecret
containers:
- image: hub.hc.com/library/myapp:v1
name: db
volumeMounts:
- name: secrets
mountPath: /etc/config
[root@master secert]
pod/secret-test created
[root@master secert]
/
/etc/config
1f2d1e2e67df
admin
將 Secret
匯出到環境變數中:
[root@master secert]
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: pod-deployment
spec:
replicas: 2
template:
metadata:
labels:
app: pod-deployment
spec:
containers:
- name: pod-1
image: hub.hc.com/library/myapp:v1
command: [ "/bin/sh", "-c", "env" ]
ports:
- containerPort: 80
env:
- name: TEST_USER
valueFrom:
secretKeyRef:
name: mysecret
key: username
- name: TEST_PASSWORD
valueFrom:
secretKeyRef:
name: mysecret
key: password
[root@master secert]
deployment.extensions/pod-deployment created
2.3 kubernetes.io/dockerconfigjson
[root@master secert]
secret "myregistrykey" created
[root@master secert]
apiVersion: v1
kind: Pod
metadata:
name: foo
spec:
containers:
- name: foo
image: roc/awangyang:v1
imagePullSecrets:
- name: myregistrykey
三、Volume
容器磁碟上的檔案的生命週期是短暫的,這就使得在容器中執行重要應用時會出現一些問題。首先,當容器崩潰時, kubelet
會重啟它,但是容器中的檔案將丟失,容器以乾淨的狀態(映象最初的狀態)重新啟動。其次,在 Pod
中同時執行多個容器時,這些容器之間通常需要共享檔案。 Kubernetes
中的 Volume
抽象就很好的解決了這些問題。
Kubernetes
中的卷( Volume
)有明確的壽命,與封裝它的 Pod
相同。所f以,卷的生命比 Pod
中的所有容器都長,當這個容器重啟時資料仍然得以儲存。當然,當 Pod
不再存在時,卷也將不復存在。 Kubernetes
支援多種型別的卷, Pod
可以同時使用任意數量的卷。
3.1 emptyDir
當 Pod
被分配給節點時,首先建立 emptyDir
卷,並且只要該 Pod
在該節點上執行,該卷就會存在。正如卷的名字所述,它最初是空的。 Pod
中的容器可以讀取和寫入 emptyDir
卷中的相同檔案,儘管該卷可以掛載到每個容器中的相同或不同路徑上。當出於任何原因從節點中刪除 Pod
時, emptyDir
中的資料將被永久刪除。
emptyDir
的用法有:
- 暫存空間,例如用於基於磁碟的合併排序
- 用作長時間計算崩潰恢復時的檢查點
Web
伺服器容器提供資料時,儲存內容管理器容器提取的檔案
[root@master volume]
apiVersion: v1
kind: Pod
metadata:
name: test-pd
spec:
containers:
- image: hub.hc.com/library/myapp:v1
name: test-container
volumeMounts:
- mountPath: /cache
name: cache-volume
- image: busybox
name: test-container2
imagePullPolicy: IfNotPresent
command: ['/bin/sh','-c','sleep 3600']
volumeMounts:
- mountPath: /test
name: cache-volume
volumes:
- name: cache-volume
emptyDir: {}
[root@master volume]
[root@master volume]
'NAME READY STATUS RESTARTS AGE
test-pd 2/2 Running 0 8m32s
[root@master volume]
/
/cache
/cache
Wed Aug 12 07:43:08 UTC 2020
[root@master ~]
/
/test
Wed Aug 12 07:43:08 UTC 2020
3.2 hostPath
hostPath
卷將主機節點的檔案系統中的檔案或目錄掛載到叢集中
hostPath
的用途如下:
- 執行需要訪問
Docker
內部的容器;使用/var/lib/docker
的hostPath
- 在容器中執行
cAdvisor
;使用/dev/cgroups
的hostPath
除了所需的 path
屬性之外,使用者還可以為 hostPath
卷指定 type
:
值行為空空字串(預設)用於向後相容,這意味著在掛載 hostPath 卷之前不會執行任何檢查DirectoryOrCreate如果在給定的路徑上沒有任何東西存在,那麼將根據需要在那裡建立一個空目錄,許可權設定為0755,與Kubelet具有相同的組和所有權。Directory給定的路徑下必須存在目錄FileOrCreate如果在給定的路徑上沒有任何東西存在,那麼會根據需要建立一個空檔案,許可權設定為0644,與Kubelet具有相同的組和所有權。File給定的路徑下必須存在檔案Socket給定的路徑下必須存在UNIX套接字CharDevice給定的路徑下必須存在字元裝置BlockDevice給定的路徑下必須存在塊裝置
使用這種卷型別時請注意:
- 由於每個節點上的檔案都不同,具有相同配置(例如從
podTemplate
建立的)的pod
在不同節點上的行為可能會有所不同 - 當
Kubernetes
按照計劃新增資源感知排程時,將無法考慮hostPath
使用的資源 - 在底層主機上建立的檔案或目錄只能由
root
寫入。您需要在特權容器中以root
身份執行程式,或修改主機上的檔案許可權以便寫入hostPath
卷
[root@master volume]
apiVersion: v1
kind: Pod
metadata:
name: test-pd
spec:
containers:
- image: hub.hc.com/library/myapp:v1
name: test-container
volumeMounts:
- mountPath: /test-pd
name: test-volume
volumes:
- name: test-volume
hostPath:
path: /data
type: Directory
[root@master volume]
[root@master volume]
/
/test-pd
[root@worker1 data]
[root@worker2 data]
2020年 08月 12日 星期三 17:52:43 CST
四、PV-PVC
4.1 相關概念
① PersistentVolume(PV)
是由管理員設定的儲存,它是群集的一部分。就像節點是叢集中的資源一樣, PV
也是叢集中的資源。 PV
是 Volume
之類的卷外掛,但具有獨立於使用 PV
的 Pod
的生命週期。此 API
物件包含儲存實現的細節,即 NFS
、 iSCSI
或特定於雲供應商的儲存系統
② PersistentVolumeClaim(PVC)
是使用者儲存的請求,它與 Pod
相似。 Pod
消耗節點資源, PVC
消耗 PV
資源。 Pod
可以請求特定級別的資源( CPU
和記憶體)。宣告可以請求特定的大小和訪問模式(例如,可以以讀/寫一次或只讀多次模式掛載)
③ 靜態 pv
叢集管理員建立一些 PV
,它們帶有可供群集使用者使用的實際儲存的細節。它們存在於 Kubernetes API
中,可用於消費
④ 動態
當管理員建立的靜態 PV
都不匹配使用者的 PersistentVolumeClaim
時,叢集可能會嘗試動態地為 PVC
建立卷。此配置基於 StorageClasses
: PVC
必須請求 [儲存類],並且管理員必須建立並配置該類才能進行動態建立。宣告該類為""可以有效地禁用其動態配置要啟用基於儲存級別的動態儲存配置,叢集管理員需要啟用 API server
上的 DefaultStorageClass
[准入控制器]。例如,通過確保 DefaultStorageClass
位於 API server
元件的 --admission-control
標誌,使用逗號分隔的有序值列表中,可以完成此操作
⑤ 繫結
master
中的控制環路監視新的 PVC
,尋找匹配的 PV
(如果可能),並將它們繫結在一起。如果為新的 PVC
動態調配 PV
,則該環路將始終將該 PV
繫結到 PVC
。否則,使用者總會得到他們所請求的儲存,但是容量可能超出要求的數量。一旦 PV
和 PVC
繫結後, PersistentVolumeClaim
繫結是排他性的,不管它們是如何繫結的。 PVC
跟 PV
繫結是一對一的對映
4.2 PV說明
PVC的保護
PVC
保護的目的是確保由 pod
正在使用的 PVC
不會從系統中移除,因為如果被移除的話可能會導致資料丟失當啟用 PVC
保護 alpha
功能時,如果使用者刪除了一個 pod
正在使用的 PVC
,則該 PVC
不會被立即刪除。 PVC
的刪除將被推遲,直到 PVC
不再被任何 pod
使用
PV演示程式碼
apiVersion: v1
kind: PersistentVolumemeta
data:
name: pv0003
spec:
capacity:
storage: 5Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Recycle
storageClassName: slow
mountOptions:
- hard
- nfsvers=4.1
nfs:
path: /tmp
server: 172.17.0.2
PV型別
PV
型別以外掛形式實現。 K8S
目前支援以下外掛型別:
PV訪問模式
PV
可以以資源提供者支援的任何方式掛載到主機上。如下表所示,供應商具有不同的功能,每個 PV
的訪問模式都將被設定為該卷支援的特定模式。例如, NFS
可以支援多個讀/寫客戶端,但特定的 NFS PV
可能以只讀方式匯出到伺服器上。每個 PV
都有一套自己的用來描述特定功能的訪問模式:
ReadWriteOnce
:該卷可以被單個節點以讀/寫模式掛載ReadOnlyMany
:該卷可以被多個節點以只讀模式掛載ReadWriteMany
:該卷可以被多個節點以讀/寫模式掛載
在命令列中,訪問模式縮寫為
RWO
:ReadWriteOnce
ROX
:ReadOnlyMany
RWX
:ReadWriteMany
回收策略
Retain
(保留)——手動回收Recycle
(回收)——基本擦除(rm -rf /thevolume/*
)Delete
(刪除)——關聯的儲存資產(例如AWS EBS
、GCE PD
、Azure Disk
和OpenStack Cinder
卷)將被刪除
當前,只有 NFS
和 HostPath
支援回收策略。 AWS EBS
、 GCE PD
、 Azure Disk
和 Cinder
卷支援刪除策略
狀態
卷可以處於以下的某種狀態:
Available
(可用)——一塊空閒資源還沒有被任何宣告繫結Bound
(已繫結)——卷已經被宣告繫結Released
(已釋放)——宣告被刪除,但是資源還未被叢集重新宣告Failed
(失敗)——該卷的自動回收失敗
命令列會顯示繫結到 PV
的 PVC
的名稱
4.3 持久化演示說明 - NFS
① 安裝 NFS
伺服器
[root@master ~]
[root@master ~]
[root@master ~]
/nfs *(rw,no_root_squash,no_all_squash,sync)
[root@master ~]
[root@worker1 ~]
[root@worker1 ~]
Export list for 192.168.182.100:
/nfs *
[root@worker1 ~]
[root@worker1 /]
② 部署 PV
[root@master pv]
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv1
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: nfs
nfs:
path: /nfs
server: 192.168.182.100
[root@master pv]
persistentvolume/nfs-pv1 created
[root@master pv]
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
nfs-pv1 1Gi RWO Retain Available nfs 5s
③ 建立服務並使用 PVC
[root@master /]
[root@master /]
[root@master /]
/nfs *(rw,no_root_squash,no_all_squash,sync)
/nfs2 *(rw,no_root_squash,no_all_squash,sync)
/nfs3 *(rw,no_root_squash,no_all_squash,sync)
[root@master pv]
[root@master pv]
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv1
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: nfs
nfs:
path: /nfs
server: 192.168.182.100
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv2
spec:
capacity:
storage: 5Gi
accessModes:
- ReadOnlyMany
persistentVolumeReclaimPolicy: Retain
storageClassName: nfs
nfs:
path: /nfs2
server: 192.168.182.100
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv3
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: slow
nfs:
path: /nfs3
server: 192.168.182.100
[root@master pv]
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
nfs-pv1 1Gi RWO Retain Available nfs 23s
nfs-pv2 5Gi ROX Retain Available nfs 23s
nfs-pv3 1Gi RWX Retain Available slow 23s
[root@master pv]
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
selector:
matchLabels:
app: nginx
serviceName: "nginx"
replicas: 2
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: hub.hc.com/library/myapp:v1
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: "nfs"
resources:
requests:
storage: 1Gi
[root@master pv]
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 7s
web-1 0/1 Pending 0 5s
[root@master pv]
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
nfs-pv1 1Gi RWO Retain Bound default/www-web-0 nfs 4m40s
nfs-pv2 5Gi ROX Retain Available nfs 4m40s
nfs-pv3 1Gi RWO Retain Bound default/www-web-1 nfs 4m40s
[root@master pv]
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
web-0 1/1 Running 0 15m 10.244.2.84 worker2 <none> <none>
web-1 1/1 Running 0 15m 10.244.1.58 worker1 <none> <none>
[root@master pv]
Hello PV
[root@master pv]
Hello PV
[root@master pv]
pod "web-0" deleted
[root@master pv]
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
web-0 1/1 Running 0 3s 10.244.2.85 worker2 <none> <none>
web-1 1/1 Running 0 18m 10.244.1.58 worker1 <none> <none>
[root@master pv]
Hello PV
4.4 關於 StatefulSet
- 匹配
Pod name
( 網路標識 ) 的模式為:$(statefulset名称)-$(序号)
,比如上面的示例:web-0
、web-1
StatefulSet
為每個Pod
副本建立了一個DNS
域名,這個域名的格式為:$(podname).(headless servername)
,也就意味著服務間是通過Pod
域名來通訊而非Pod IP
,因為當Pod
所在Node
發生故障時,Pod
會被飄移到其它Node
上,Pod IP
會發生變化,但是Pod
域名不會有變化StatefulSet
使用Headless
服務來控制Pod
的域名,這個域名的FQDN
為:$(servicename).$(namespace).svc.cluster.local
,其中,cluster.local
指的是叢集的域名- 根據
volumeClaimTemplates
,為每個Pod
建立一個pvc
,pvc
的命名規則匹配模式:(volumeClaimTemplates.name)-(pod_name)
,比如上面的volumeMounts.name=www
,Podname=web-[0-2]
,因此建立出來的PVC
是www-web-0
、www-web-1
- 刪除
Pod
不會刪除其pvc
,手動刪除pvc
將自動釋放pv
Statefulset
的啟停順序:
- 有序部署:部署
StatefulSet
時,如果有多個Pod
副本,它們會被順序地建立(從0
到N-1
)並且,在下一個Pod
執行之前所有之前的Pod
必須都是Running
和Ready
狀態 - 有序刪除:當
Pod
被刪除時,它們被終止的順序是從N-1
到0
- 有序擴充套件:當對
Pod
執行擴充套件操作時,與部署一樣,它前面的Pod
必須都處於Running
和Ready
狀態
StatefulSet
使用場景:
- 穩定的持久化儲存,即Pod重新排程後還是能訪問到相同的持久化資料,基於
PVC
來實現 - 穩定的網路識別符號,即
Pod
重新排程後其PodName
和HostName
不變 - 有序部署,有序擴充套件,基於
init containers
來實現 - 有序收縮
相關文章
- 程式設計師給小孩取的名字程式設計師
- 讓程式設計師乾的爽,他們肯定願意留下程式設計師
- 讓我們成為更好的程式設計師程式設計師
- 首批程式設計師他們是如何程式設計的?程式設計師
- 吹爆了!程式設計師自學Python,不如9歲小孩,網友:我不信....程式設計師Python
- 程式設計師被懟!HR:對不起,我們不招“精通Excel”的程式設計師程式設計師Excel
- 為什麼我們需要教小孩子程式設計程式設計
- 有個碼齡 10 年的程式設計師跟我說:“他程式設計從來不用滑鼠”,我說:程式設計師
- 我的程式設計師之路程式設計師
- 讓老闆開心的程式設計師不是好程式設計師程式設計師
- 小孩子如何學程式設計程式設計
- 他的名字是Linus,他是所有程式設計師們的上帝程式設計師
- 一個程式設計師的負罪感程式設計師
- 程式設計師妻子自述:那些程式設計師教給我的程式設計師
- 程式設計師妻子自述: 那些程式設計師教給我的程式設計師
- 【程式設計師漫畫】猜猜他是誰?程式設計師
- 學成才知道,風變程式設計誠不欺我!程式設計
- 大學老師告訴我:20年內程式設計師會被取代,未來不需要程式設計師...程式設計師
- 我是程式設計師,我自豪程式設計師
- 我的程式設計師之路(六)程式設計師
- 老程式設計師被新程式設計師拍在沙灘上?程式設計師
- 我是印度程式設計師,我要為印度程式設計師辯護程式設計師
- 讓程式設計師崩潰的瞬間(非程式設計師勿入)程式設計師
- 一篇鞭策程式設計師的短文:我們這一代的汽車工人程式設計師
- 3年程式設計師寫的程式碼被應屆生懟:我能三行搞定!也配叫程式設計師?程式設計師
- 老婆讓我教她程式設計程式設計
- 程式設計會讓我發胖?!程式設計
- RoboWunderkind機器人積木玩具,讓5歲小孩也能學程式設計機器人程式設計
- 被老程式設計師壓榨怎麼辦?我不想辭職程式設計師
- 面試了一個 39 歲程式設計師後,我被罵了……面試程式設計師
- 我的程式設計師之路(七)------準程式設計師的酸甜苦辣程式設計師
- 他可能是全球最會說唱的程式設計師程式設計師
- 一篇程式設計師應該看下的文章程式設計師
- 你好,我是程式設計師程式設計師
- 我看程式設計師 (轉)程式設計師
- 我的程式設計師未婚夫程式設計師
- 我的丈夫是個程式設計師程式設計師
- 讓程式設計師失去程式設計激情的5件事程式設計師