1. بررسی اجمالی
درباره Micronaut
Micronaut یک چارچوب مدرن، مبتنی بر JVM و تمام پشته برای ساخت برنامههای میکروسرویس و بدون سرور ماژولار است که به راحتی قابل آزمایش است. هدف Micronaut ارائه زمان راه اندازی عالی، توان عملیاتی سریع، با حداقل ردپای حافظه است. توسعه دهندگان می توانند با Micronaut در جاوا، Groovy یا Kotlin توسعه دهند.
Micronaut ارائه می دهد:
- زمان راهاندازی سریع و مصرف کم حافظه - چارچوبهای IoC مبتنی بر انعکاس، دادههای بازتابی حافظه پنهان را برای هر فیلد، روش و سازنده کد شما بارگذاری میکنند، در حالی که با Micronaut، زمان راهاندازی برنامه و مصرف حافظه شما به اندازه شما محدود نمیشود. پایگاه کد.
- کلاینت HTTP اعلامی، واکنشی، زمان کامپایل - کلاینت های HTTP واکنشی را به صورت اعلامی بسازید، که در زمان کامپایل پیاده سازی می شوند و مصرف حافظه را کاهش می دهند.
- سرور HTTP غیر مسدود کننده ساخته شده بر روی Netty — با منحنی یادگیری نرم، سرور HTTP Micronaut افشای API هایی را که می توانند توسط سرویس گیرندگان HTTP مصرف شوند، تا حد امکان آسان می کند.
- تست سریع و آسان - به راحتی سرورها و کلاینت ها را در تست های واحد خود بچرخانید و آنها را بی درنگ اجرا کنید.
- تزریق وابستگی زمان کامپایل کارآمد و AOP — Micronaut یک API برنامه نویسی جنبه محور ساده در زمان کامپایل ارائه می دهد که از بازتاب استفاده نمی کند.
- برنامههای کاملا واکنشگرا و غیرمسدود بسازید - Micronaut از هر چارچوبی که Reactive Streams را اجرا میکند، از جمله RxJava و Reactor پشتیبانی میکند.
برای اطلاعات بیشتر، لطفاً به وب سایت Micronaut مراجعه کنید.
درباره Kubernetes
Kubernetes یک پروژه منبع باز است که میتواند در محیطهای مختلف، از لپتاپ گرفته تا خوشههای چند گرهای با در دسترس بودن بالا، از ابرهای عمومی تا استقرار در محل، از ماشینهای مجازی تا فلز خالی اجرا شود.
در این آزمایشگاه، شما یک میکروسرویس ساده مبتنی بر Groovy Micronaut را برای Kubernetes اجرا میکنید که در موتور Kubernetes اجرا میشود.
هدف این کد لبه این است که شما میکروسرویس خود را به عنوان یک سرویس تکراری در حال اجرا در Kubernetes اجرا کنید. شما کدی را که روی دستگاه خود توسعه داده اید، می گیرید، آن را به یک تصویر ظرف داکر تبدیل می کنید و سپس آن تصویر را در موتور Kubernetes اجرا می کنید.
در اینجا نموداری از قسمت های مختلف در حال بازی در این کد لبه وجود دارد تا به شما کمک کند تا بفهمید که چگونه قطعات با هم قرار می گیرند. از این به عنوان یک مرجع در حین پیشرفت در نرم افزار کد استفاده کنید. تا زمانی که به انتها برسید، همه چیز باید منطقی باشد (اما فعلاً این را نادیده بگیرید).
برای اهداف این نرم افزار کد، استفاده از یک محیط مدیریت شده مانند Kubernetes Engine (نسخه میزبان گوگل Kubernetes که بر روی Compute Engine اجرا می شود) به شما این امکان را می دهد که به جای راه اندازی زیرساخت های زیربنایی، بر روی تجربه Kubernetes تمرکز کنید.
اگر علاقه مند به اجرای Kubernetes بر روی دستگاه محلی خود، مانند یک لپ تاپ توسعه، هستید، احتمالاً باید Minikube را بررسی کنید. این یک راه اندازی ساده از یک خوشه kubernetes تک گره را برای اهداف توسعه و آزمایش ارائه می دهد. در صورت تمایل می توانید از Minikube برای مرور این کدها استفاده کنید.
درباره جیب
Jib یک ابزار متن باز است که به شما امکان می دهد تصاویر Docker و OCI را برای برنامه های جاوا خود بسازید. این افزونه برای Maven و Gradle و به عنوان یک کتابخانه جاوا در دسترس است.
هدف جیب این است که:
- سریع - تغییرات خود را سریع اجرا کنید. Jib برنامه شما را به چندین لایه جدا می کند و وابستگی ها را از کلاس ها جدا می کند. اکنون لازم نیست منتظر بمانید تا Docker کل برنامه جاوا شما را بازسازی کند - فقط لایه هایی را که تغییر کرده اند مستقر کنید.
- قابل تکرار - بازسازی تصویر ظرف خود با محتویات یکسان همیشه یک تصویر را ایجاد می کند. هرگز دوباره به روز رسانی غیر ضروری را راه اندازی نکنید.
- بدون دیمون - وابستگی های CLI خود را کاهش دهید. تصویر Docker خود را از داخل Maven یا Gradle بسازید و به هر رجیستری مورد نظر خود فشار دهید. دیگر نیازی به نوشتن Dockerfiles و فراخوانی docker build/push نیست.
می توانید اطلاعات بیشتری درباره Jib در صفحه پروژه Github بیابید.
در مورد این آموزش
این آموزش از کد نمونه ابزار Jib برای ساخت کانتینر برای برنامه های جاوا استفاده می کند.
نمونه یک سرویس ساده hello world است که از چارچوب Micronaut و زبان برنامه نویسی Apache Groovy استفاده می کند.
چیزی که یاد خواهید گرفت
- نحوه بسته بندی یک برنامه ساده جاوا به عنوان یک ظرف Docker با استفاده از Jib
- چگونه خوشه Kubernetes خود را در Kubernetes Engine ایجاد کنید.
- نحوه استقرار سرویس Micronaut خود در Kubernetes در موتور Kubernetes
- چگونه خدمات خود را افزایش دهید و ارتقا دهید.
- نحوه دسترسی به داشبورد گرافیکی Kubernetes
آنچه شما نیاز دارید
- یک پروژه Google Cloud Platform
- یک مرورگر، مانند کروم یا فایرفاکس
- آشنایی با ویرایشگرهای متن استاندارد لینوکس مانند Vim، EMACs یا Nano
چگونه از این آموزش استفاده خواهید کرد؟
تجربه خود را با ساختن برنامه های وب HTML/CSS چگونه ارزیابی می کنید؟
تجربه خود را در استفاده از سرویسهای پلتفرم ابری Google چگونه ارزیابی میکنید؟
2. راه اندازی و الزامات
تنظیم محیط خود به خود
- به کنسول Cloud وارد شوید و یک پروژه جدید ایجاد کنید یا از یک موجود استفاده مجدد کنید. (اگر قبلاً یک حساب Gmail یا G Suite ندارید، باید یک حساب ایجاد کنید .)
شناسه پروژه را به خاطر بسپارید، یک نام منحصر به فرد در تمام پروژه های Google Cloud (نام بالا قبلاً گرفته شده است و برای شما کار نخواهد کرد، متأسفیم!). بعداً در این آزمایشگاه کد به عنوان PROJECT_ID
نامیده خواهد شد.
- در مرحله بعد، برای استفاده از منابع Google Cloud، باید صورتحساب را در Cloud Console فعال کنید .
اجرا کردن از طریق این کد لبه نباید هزینه زیادی داشته باشد، اگر اصلاً باشد. حتماً دستورالعملهای موجود در بخش «تمیز کردن» را دنبال کنید که به شما توصیه میکند چگونه منابع را خاموش کنید تا بیش از این آموزش متحمل صورتحساب نشوید. کاربران جدید Google Cloud واجد شرایط برنامه آزمایشی رایگان 300 دلاری هستند.
3. کد منبع نمونه Micronaut را دریافت کنید
پس از راهاندازی Cloud Shell، میتوانید از خط فرمان برای شبیهسازی کد منبع مثال در فهرست اصلی، و cd را در دایرکتوری حاوی سرویس نمونه ما استفاده کنید:
$ git clone https://github.com/GoogleContainerTools/jib.git
$ cd jib/examples/micronaut/
4. نگاهی گذرا به کد
سرویس ساده Micronaut ما از یک کنترلر ساخته شده است که پیام بدنام Hello World را صادر می کند:
@Controller("/hello") class HelloController { @Get("/") String index() { "Hello World" } }
کنترلر HelloController
به درخواست ها در مسیر /hello
پاسخ می دهد و متد index()
درخواست های HTTP GET را می پذیرد.
یک کلاس تست Spock نیز برای بررسی اینکه پیام صحیح در خروجی داده شده است موجود است.
class HelloControllerSpec extends Specification { @Shared @AutoCleanup EmbeddedServer embeddedServer = ApplicationContext.run(EmbeddedServer) @Shared @AutoCleanup RxHttpClient client = embeddedServer.applicationContext.createBean(RxHttpClient, embeddedServer.getURL()) void "test hello world response"() { when: HttpRequest request = HttpRequest.GET('/hello') String rsp = client.toBlocking().retrieve(request) then: rsp == "Hello World" } }
بیش از یک تست واحد ساده، این تست در واقع همان پشته سرور Micronaut (بر اساس چارچوب Netty ) را اجرا می کند که در مرحله تولید اجرا می شود. بنابراین رفتار کد شما در محصول همانند تست های شما خواهد بود.
برای اجرای تست ها می توانید دستور زیر را اجرا کنید تا ببینید همه چیز درست است:
./gradlew test
5. برنامه را به صورت محلی اجرا کنید
می توانید سرویس Micronaut را به طور معمول با دستور Gradle زیر راه اندازی کنید:
$ ./gradlew run
هنگامی که برنامه شروع به کار کرد، می توانید یک نمونه اضافی Cloud Shell را به لطف نماد + کوچک باز کنید و سپس با curl بررسی کنید که خروجی مورد انتظار را دریافت کنید:
$ curl localhost:8080/hello
و باید یک پیام ساده "Hello World" ظاهر شود.
6. برنامه را به عنوان یک ظرف Docker با Jib بسته بندی کنید
در مرحله بعد، برنامه خود را برای اجرا در Kubernetes آماده کنید. برای این منظور، ما از جیب استفاده خواهیم کرد تا کارهای سخت را برای ما انجام دهد، زیرا نیازی نیست خودمان به یک Dockerfile
دست بزنیم!
بیایید دستور را برای ساخت کانتینر خود اجرا کنیم:
$ ./gradlew jibDockerBuild
این خروجی است که باید ببینید:
Tagging image with generated image reference micronaut-jib:0.1. If you'd like to specify a different tag, you can set the jib.to.image parameter in your build.gradle, or use the --im age=<MY IMAGE> commandline flag. Containerizing application to Docker daemon as micronaut-jib:0.1... warning: Base image 'gcr.io/distroless/java' does not use a specific image digest - build may not be reproducible Getting base image gcr.io/distroless/java... Building dependencies layer... Building resources layer... Building classes layer... Finalizing... Container entrypoint set to [java, -cp, /app/resources:/app/classes:/app/libs/*, example.micronaut.Application] Loading to Docker daemon... Built image to Docker daemon as micronaut-jib:0.1
اکنون که تصویر ما ساخته شده است، بیایید بررسی کنیم که آیا می توانیم پیام سلام دوستانه خود را با اجرای تصویر Docker خود در اولین تب Cloud Shell مشاهده کنیم:
$ docker run -it -p 8080:8080 micronaut-jib:0.1 16:57:20.255 [main] INFO i.m.context.env.DefaultEnvironment - Established active environments: [cloud, gcp] 16:57:23.203 [main] INFO io.micronaut.runtime.Micronaut - Startup completed in 2926ms. Server Running: http://97b7d76ccf3f:8080
سرویس ما در حال اجرا است، بنابراین اکنون میتوانیم دستور curl خود را در برگه دوم Cloud Shell راهاندازی کنیم تا ببینیم آیا مطابق انتظار کار میکند یا خیر:
$ curl localhost:8080/hello Hello World
میتوانید با زدن Ctrl+C
در Cloud Shell، کانتینر را متوقف کنید.
7. فشار دادن سرویس کانتینری ما به رجیستری
اکنون که تصویر طبق برنامه کار میکند، میتوانید آن را به Google Container Registry فشار دهید، یک مخزن خصوصی برای تصاویر Docker شما که از هر پروژه Google Cloud (و همچنین از خارج از Google Cloud Platform) قابل دسترسی است.
قبل از اینکه بتوانیم به رجیستری فشار بیاوریم، با رفتن به Tools > Container Registry، مطمئن شویم که Container Registry برای پروژه ما فعال است. اگر فعال نیست، باید کادر گفتگوی زیر را مشاهده کنید، سپس روی " فعال کردن Container Registry API " کلیک کنید تا فعال شود:
پس از آماده شدن رجیستری، برای فشار دادن تصویر به رجیستری، دستورات زیر را اجرا کنید:
$ gcloud auth configure-docker $ docker tag micronaut-jib:0.1 \ gcr.io/$GOOGLE_CLOUD_PROJECT/micronaut-jib:0.1 $ docker push gcr.io/$GOOGLE_CLOUD_PROJECT/micronaut-jib:0.1
دستورات بالا به gcloud SDK اجازه پیکربندی و اجازه به داکر را میدهد تا تصاویر را به نمونه Container Registry فشار دهد، تصویر را به مکان خود در رجیستری نشان دهد و سپس آن را به رجیستری فشار دهد.
اگر همه چیز خوب پیش رفت و پس از مدتی، باید بتوانید تصویر ظرف فهرست شده در کنسول را ببینید: Tools > Container Registry . در این مرحله شما اکنون یک تصویر Docker در سطح پروژه در دسترس دارید که Kubernetes می تواند به آن دسترسی داشته باشد و همانطور که در عرض چند دقیقه خواهید دید آن را هماهنگ کند.
8. خوشه خود را ایجاد کنید
خوب، شما اکنون آماده هستید تا خوشه موتور Kubernetes خود را ایجاد کنید، اما قبل از آن، به بخش Google Kubernetes Engine در کنسول وب بروید و منتظر بمانید تا سیستم راه اندازی شود (فقط چند ثانیه طول می کشد).
یک خوشه از یک سرور API اصلی Kubernetes که توسط Google مدیریت می شود و مجموعه ای از گره های کارگر تشکیل شده است. گره های کارگر ماشین های مجازی Compute Engine هستند. بیایید از gcloud
CLI از جلسه CloudShell خود برای ایجاد یک کلاستر با دو گره n1-standard-1
استفاده کنیم (تکمیل این کار چند دقیقه طول می کشد):
$ gcloud container clusters create hello-cluster \ --num-nodes 2 \ --machine-type n1-standard-1 \ --zone us-central1-c
در پایان باید خوشه ایجاد شده را مشاهده کنید.
Creating cluster hello-cluster in us-central1-c...done. Created [https://container.googleapis.com/v1/projects/mn-gke-test/zones/us-central1-c/clusters/hello-cluster]. To inspect the contents of your cluster, go to: https://console.cloud.google.com/kubernetes/workload_/gcloud/us-central1-c/hello-cluster?project=mn-gke-test kubeconfig entry generated for hello-cluster. NAME LOCATION MASTER_VERSION MASTER_IP MACHINE_TYPE NODE_VERSION NUM_NODES STATUS hello-cluster us-central1-c 1.9.7-gke.7 35.239.224.115 n1-standard-1 1.9.7-gke.7 2 RUNNING
اکنون باید یک خوشه Kubernetes کاملاً عملکردی داشته باشید که توسط Google Kubernetes Engine طراحی شده است:
اکنون زمان آن است که برنامه کانتینری خود را در خوشه Kubernetes مستقر کنید! از این پس از خط فرمان kubectl
(که قبلاً در محیط Cloud Shell شما راه اندازی شده است) استفاده خواهید کرد. بقیه این کد لبه نیاز دارند که هم نسخه سرویس گیرنده و هم نسخه سرور Kubernetes 1.2 یا بالاتر باشد. kubectl version
نسخه فعلی دستور را به شما نشان می دهد.
9. برنامه خود را در Kubernetes مستقر کنید
استقرار Kubernetes می تواند چندین نمونه از برنامه شما را با استفاده از تصویر کانتینری که به تازگی ایجاد کرده اید ایجاد، مدیریت و مقیاس بندی کند. بیایید با استفاده از دستور kubectl create deployment
یک استقرار از برنامه شما در Kubernetes ایجاد کنیم:
$ kubectl create deployment hello-micronaut \ --image=gcr.io/$GOOGLE_CLOUD_PROJECT/micronaut-jib:0.1
برای مشاهده استقراری که ایجاد کرده اید، به سادگی اجرا کنید:
$ kubectl get deployments NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE hello-micronaut 1 1 1 1 5m
برای مشاهده نمونه های برنامه ایجاد شده توسط Deployment، این دستور را اجرا کنید:
$ kubectl get pods NAME READY STATUS RESTARTS AGE hello-micronaut-5647fb98c5-lh5h7 1/1 Running 0 5m
در این مرحله شما باید کانتینر خود را تحت کنترل Kubernetes قرار دهید، اما همچنان باید آن را برای دنیای خارج قابل دسترس کنید.
10. اجازه ترافیک خارجی را بدهید
بهطور پیشفرض، غلاف تنها با IP داخلی آن در خوشه قابل دسترسی است. برای اینکه کانتینر hello-micronaut
از خارج از شبکه مجازی kubernetes قابل دسترسی باشد، باید pod را به عنوان یک سرویس kubernetes در معرض دید قرار دهید.
از Cloud Shell میتوانید با دستور kubectl expose
همراه با پرچم --type=LoadBalancer
پاد را در اینترنت عمومی قرار دهید. این پرچم برای ایجاد یک IP قابل دسترسی خارجی مورد نیاز است:
$ kubectl expose deployment hello-micronaut --type=LoadBalancer --port=8080
پرچم استفاده شده در این دستور مشخص می کند که از بار متعادل کننده ارائه شده توسط زیرساخت اصلی (در این مورد Compute Engine Load Balancer ) استفاده می کنید. توجه داشته باشید که استقرار را در معرض نمایش می گذارید، نه پاد را مستقیما. این باعث میشود که سرویس بهدستآمده، ترافیک تعادلی را در تمام پادهای مدیریت شده توسط استقرار بارگیری کند (در این مورد فقط 1 پاد، اما بعداً کپیهای بیشتری اضافه خواهید کرد).
استاد Kubernetes متعادل کننده بار و قوانین حمل و نقل موتور محاسباتی مرتبط، استخرهای هدف و قوانین فایروال را ایجاد می کند تا سرویس را به طور کامل از خارج از Google Cloud Platform در دسترس قرار دهد.
برای یافتن آدرس IP قابل دسترسی عمومی سرویس، به سادگی از kubectl
درخواست کنید تا تمام خدمات خوشه را فهرست کند:
$ kubectl get services NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE hello-micronaut LoadBalancer 10.39.243.251 aaa.bbb.ccc.ddd 8080:30354/TCP 1m kubernetes ClusterIP 10.39.240.1 <none> 443/TCP 31m
توجه داشته باشید که 2 آدرس IP برای سرویس شما فهرست شده است که هر دو درگاه 8080
را ارائه می دهند. یکی IP داخلی است که فقط در داخل شبکه مجازی ابری شما قابل مشاهده است. دیگری IP خارجی با بار متعادل است. در این مثال، آدرس IP خارجی aaa.bbb.ccc.ddd
است.
اکنون باید بتوانید با اشاره مرورگر خود به این آدرس به سرویس دسترسی پیدا کنید: http://<EXTERNAL_IP>
:8080
/hello
11. خدمات خود را افزایش دهید
یکی از ویژگی های قدرتمند ارائه شده توسط Kubernetes این است که مقیاس کردن برنامه شما چقدر آسان است. فرض کنید به طور ناگهانی به ظرفیت بیشتری برای برنامه خود نیاز دارید. شما به سادگی می توانید به کنترل کننده تکرار بگویید که تعداد جدیدی از replica ها را برای نمونه های برنامه شما مدیریت کند:
$ kubectl scale deployment hello-micronaut --replicas=3 deployment.extensions "hello-micronaut" scaled $ kubectl get deployment NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE hello-micronaut 3 3 3 3 16m
در اینجا به رویکرد اعلامی توجه کنید - به جای شروع یا توقف نمونههای جدید، تعداد نمونههایی را که همیشه باید در حال اجرا باشند، اعلام میکنید. حلقه های آشتی Kubernetes به سادگی اطمینان حاصل می کنند که واقعیت با آنچه شما درخواست کرده اید مطابقت دارد و در صورت نیاز اقدام می کند.
12. سرویس خود را ارتقا دهید
در برخی مواقع برنامهای که برای تولید مستقر کردهاید به رفع اشکال یا ویژگیهای اضافی نیاز دارد. Kubernetes اینجاست تا به شما کمک کند تا یک نسخه جدید را بدون تأثیرگذاری بر روی کاربران خود به تولید بفرستید.
ابتدا بیایید برنامه را اصلاح کنیم. ویرایشگر کد را از Cloud Shell باز کنید.
به /jib/examples/micronaut/src/main/groovy/example/micronaut/HelloController.groovy
بروید و مقدار پاسخ را بهروزرسانی کنید:
@Controller("/hello") class HelloController { @Get("/") String index() { "Hello Kubernetes World" } }
در /jib/examples/micronaut/build.gradle
، با بهروزرسانی این خط، نسخه تصویر خود را از 0.1 به 0.2 ارتقا میدهیم:
version '0.2'
سپس برنامه را با آخرین تغییرات بازسازی و بسته بندی کنید:
$ ./gradlew jibDockerBuild
و تصویر را تگ کنید و به رجیستری تصویر ظرف فشار دهید:
$ docker tag micronaut-jib:0.2 \ gcr.io/$GOOGLE_CLOUD_PROJECT/micronaut-jib:0.2 $ docker push gcr.io/$GOOGLE_CLOUD_PROJECT/micronaut-jib:0.2
اکنون برای Kubernetes آمادهاید تا بهراحتی کنترلر تکرار شما را به نسخه جدید برنامه بهروزرسانی کند. برای تغییر برچسب تصویر کانتینر در حال اجرا، باید hello-micronaut deployment
موجود را ویرایش کنید و تصویر را از gcr.io/PROJECT_ID/micronaut-jib:0.1
به gcr.io/PROJECT_ID/micronaut-jib:0.2
تغییر دهید. .
میتوانید از دستور kubectl set image
استفاده کنید تا از Kubernetes بخواهید نسخه جدید برنامه شما را در کل خوشه بهصورت یکبار با بهروزرسانی رولینگ مستقر کند:
$ kubectl set image deployment/hello-micronaut \ micronaut-jib=gcr.io/$GOOGLE_CLOUD_PROJECT/micronaut-jib:0.2 deployment.apps "hello-micronaut" image updated
دوباره http://EXTERNAL_IP:8080 را بررسی کنید تا ببینید پاسخ جدید را برمیگرداند.
13. به عقب برگردید
اوه - آیا با نسخه جدید برنامه اشتباه کردید؟ شاید نسخه جدید دارای خطایی باشد و شما باید سریع آن را برگردانید. با Kubernetes، می توانید به راحتی به حالت قبلی برگردید. بیایید با اجرای برنامه، برنامه را به عقب برگردانیم:
$ kubectl rollout undo deployment/hello-micronaut
اگر به خروجی این سرویس نگاهی بیندازید، به پیام اولیه "سلام جهان" باز خواهیم گشت.
14. خلاصه
در این مرحله، یک سرویس ساده Micronaut hello world مبتنی بر Apache Groovy را راهاندازی میکنید و آن را مستقیماً از داخل Cloud Shell اجرا میکنید، آن را به عنوان یک ظرف با Jib بستهبندی میکنید و آن را در Google Kubernetes Engine مستقر میکنید.
15. تبریک می گویم!
شما یاد گرفتید که چگونه یک میکروسرویس جدید مبتنی بر وب Apache Groovy / Micronaut را در Kubernetes در موتور Google Kubernetes بسازید.
بیشتر بدانید
- اسناد و نمونه های Jib: https://github.com/GoogleContainerTools/jib/
- وب سایت Micronaut: http://micronaut.io/
- جاوا در Google Cloud Platform: https://cloud.google.com/java/
- برای مثال های جاوا: https://cloud.google.com/java/samples
- برای آموزش طولانیتر و کاملتر Kubernetes، به bit.ly/k8s-lab مراجعه کنید، جایی که شما را از طریق استقرار یک برنامه فول استک راهنمایی میکند.
مجوز
این اثر تحت مجوز Creative Commons Attribution 2.0 Generic مجوز دارد.