Google Cloud पर वसंत के समय डेटा

1. खास जानकारी

इस कोडलैब में, हम Spring Native के प्रोजेक्ट के बारे में जानेंगे. साथ ही, हम ऐसा ऐप्लिकेशन बनाने के बारे में जानेंगे जो उसका इस्तेमाल करता है और उसे Google Cloud पर डिप्लॉय करेगा.

हम इसके कॉम्पोनेंट, प्रोजेक्ट का हाल ही का इतिहास, इस्तेमाल के कुछ उदाहरण, और अपने प्रोजेक्ट में इसे इस्तेमाल करने की ज़रूरी जानकारी पर चर्चा करेंगे.

Spring Native का प्रोजेक्ट अभी प्रयोग के तौर पर है. इसलिए, इसे शुरू करने के लिए कुछ खास कॉन्फ़िगरेशन की ज़रूरत होगी. हालांकि, जैसा कि SpringOne 2021 में एलान किया गया था, Spring Native को फ़र्स्ट क्लास सहायता के साथ Spring Framework 6.0 और Spring बूट 3.0 में इंटिग्रेट किया जा सकता है, इसलिए रिलीज़ से कुछ महीने पहले इस प्रोजेक्ट को करीब से देखने का यह सही समय है.

हालांकि, लंबे समय तक चलने वाली प्रोसेस जैसी चीज़ों के लिए, रीयल-टाइम में डेटा इकट्ठा करने की सुविधा को बहुत अच्छी तरह से ऑप्टिमाइज़ किया गया है. हालांकि, इस्तेमाल के कुछ ऐसे मामले हैं जिनमें समय से पहले कंपाइल किए गए ऐप्लिकेशन और बेहतर परफ़ॉर्म करते हैं. इसके बारे में हम कोडलैब के दौरान चर्चा करेंगे.

आपको इनके बारे में जानकारी मिलेगी

  • Cloud Shell का इस्तेमाल करना
  • Cloud Run API चालू करना
  • Spring Native ऐप्लिकेशन बनाएं और डिप्लॉय करें
  • ऐसे किसी ऐप्लिकेशन को Cloud Run पर डिप्लॉय करें

आपको इन चीज़ों की ज़रूरत होगी

सर्वे

इस ट्यूटोरियल का इस्तेमाल कैसे किया जाएगा?

इसे सिर्फ़ पढ़ें इसे पढ़ें और कसरतों को पूरा करें

Java के साथ अपने अनुभव को आप कितनी रेटिंग देंगे?

शुरुआती इंटरमीडिएट कुशल

Google Cloud की सेवाएं इस्तेमाल करने का आपका अनुभव कैसा रहा?

शुरुआती इंटरमीडिएट कुशल

2. बैकग्राउंड

Spring Native प्रोजेक्ट, डेवलपर को नेटिव ऐप्लिकेशन की परफ़ॉर्मेंस देने के लिए कई टेक्नोलॉजी का इस्तेमाल करता है.

Spring Native को पूरी तरह से समझने के लिए, इनमें से कुछ कॉम्पोनेंट टेक्नोलॉजी को समझना आपके लिए मददगार होगा. साथ ही, यह जानना भी ज़रूरी है कि ये टेक्नोलॉजी हमारे लिए क्या काम करती हैं और ये यहां एक साथ कैसे काम करती हैं.

एओटी कंपाइलेशन

जब डेवलपर कंपाइल के समय सामान्य रूप से javac चलाते हैं, तो हमारे .java सोर्स कोड को .class फ़ाइलों में कंपाइल किया जाता है, जो बाइटकोड में लिखी जाती हैं. इस बाइट कोड को सिर्फ़ Java वर्चुअल मशीन समझ सकता है. इसलिए, हमें अपना कोड चलाने के लिए, JVM को अन्य मशीनों पर इस कोड की व्याख्या करनी होगी.

यह प्रक्रिया हमें Java की हस्ताक्षर पोर्टेबिलिटी देती है - जिससे हम "एक बार लिखने और हर जगह चलाने" की अनुमति देते हैं, लेकिन स्थानीय कोड चलाने की तुलना में यह महंगी है.

अच्छी बात यह है कि जेवीएम को लागू करने के लिए, रीयल टाइम में डेटा इकट्ठा करने की सुविधा का इस्तेमाल किया जाता है. इससे, जानकारी देने में आने वाले खर्च को कम किया जा सकता है. इसके लिए, किसी फ़ंक्शन के शुरू करने वालों की गिनती की जाती है. साथ ही, अगर किसी सीमा ( डिफ़ॉल्ट रूप से 10,000) को पार करने के लिए बार-बार उसे इस्तेमाल किया जाता है, तो उसे रन टाइम के दौरान नेटिव कोड में इकट्ठा किया जाता है, ताकि और महंगी जानकारी न मिल पाए.

समय बीतने के बाद कंपाइलेशन करने का तरीका उलट होता है. इसके लिए, कंपाइल किए जा सकने वाले सभी कोड को कंपाइल करते समय नेटिव एक्ज़ीक्यूटेबल में कंपाइल किया जाता है. इससे, रन टाइम के दौरान मेमोरी की क्षमता और परफ़ॉर्मेंस में होने वाली अन्य बढ़ोतरी के लिए, पोर्टेबिलिटी को इस्तेमाल किया जाता है.

5042e8e62a05a27.png

बेशक यह एक ट्रेड ऑफ़ है, और हमेशा इसका फ़ायदा नहीं उठाता. हालांकि, कुछ मामलों में एओटी को कंपाइल किया जा सकता है. जैसे:

  • कम समय तक चलने वाले ऐसे ऐप्लिकेशन जहां स्टार्टअप समय ज़रूरी होता है
  • ज़्यादा मेमोरी की कमी वाले एनवायरमेंट में JIT का खर्च बढ़ सकता है

एक मज़ेदार बात यह है कि JDK 9 में, एओटी को जोड़कर बनाने की सुविधा को एक्सपेरिमेंट के तौर पर उपलब्ध सुविधा के तौर पर पेश किया गया था. हालांकि, इसे लागू करना महंगा था और इस पर कभी काम नहीं किया गया. इसलिए, Java 17 में इसे चुपचाप हटा दिया गया, ताकि सिर्फ़ GraalVM का इस्तेमाल करने वाले डेवलपर को फ़ायदा मिल सके.

GraalVM

GraalVM, पूरी तरह से ऑप्टिमाइज़ किया गया एक ओपन सोर्स JDK डिस्ट्रिब्यूशन है. इसमें, स्टार्टअप को बहुत तेज़ी से शुरू करने की सुविधा, एओटी नेटिव इमेज कंपाइलेशन, और कई तरह की पॉलीग्लॉट सुविधाएं हैं. इससे डेवलपर कई भाषाओं को एक ही ऐप्लिकेशन में शामिल कर पाते हैं.

GraalVM पर काम चल रहा है, वह हमेशा नई-नई सुविधाएं हासिल कर रही है, और मौजूदा क्षमताओं को बेहतर बना रही है. इसलिए, मैं डेवलपर को सलाह देती रहती हूं कि वे हमारे साथ बने रहें.

हाल की कुछ उपलब्धियां ये हैं:

  • नया और उपयोगकर्ताओं के लिए आसान नेटिव इमेज बिल्ड आउटपुट ( 18-01-2021)
  • Java 17 सहायता ( 18-01-2022)
  • पॉलीग्लोट कंपाइलेशन समय बेहतर बनाने के लिए, डिफ़ॉल्ट रूप से मल्टी-टीयर कंपाइलेशन चालू करना ( 20-04-2021)

वसंत के मौसम के हिसाब से मूल भाषा

आसान शब्दों में - Spring Native, GraalVM के नेटिव-इमेज कंपाइलर का इस्तेमाल करने की सुविधा देता है, ताकि Spring ऐप्लिकेशन को नेटिव एक्ज़ीक्यूटेबल में बदला जा सके.

इस प्रोसेस में, कंपाइल किए जाते समय आपके ऐप्लिकेशन का स्टैटिक विश्लेषण करना शामिल है. इससे, आपके ऐप्लिकेशन में उन सभी तरीकों को खोजा जा सकता है जिन पर एंट्री पॉइंट से पहुंचा जा सकता है.

इससे एक "बंद दुनिया" बन जाती है है, जहां यह माना जाता है कि पूरे कोड को कंपाइल करते समय पता चल जाएगा. साथ ही, रनटाइम पर किसी नए कोड को लोड करने की अनुमति नहीं होती.

यह ध्यान रखना ज़रूरी है कि नेटिव इमेज जनरेट करने की प्रोसेस, मेमोरी पर आधारित होती है. इसमें किसी नियमित ऐप्लिकेशन को कंपाइल करने के मुकाबले ज़्यादा समय लगता है. साथ ही, यह Java के कुछ पहलुओं पर सीमाएं लागू करती है.

कुछ मामलों में, किसी ऐप्लिकेशन को Spring Native के साथ काम करने के लिए कोड में कोई बदलाव करने की ज़रूरत नहीं होती. हालांकि, कुछ स्थितियों में ठीक से काम करने के लिए खास नेटिव कॉन्फ़िगरेशन की ज़रूरत होती है. ऐसे मामलों में, Spring Native इस प्रोसेस को आसान बनाने के लिए अक्सर नेटिव हिंट देता है.

3. सेटअप/प्रीवर्क

Spring Native को लागू करने से पहले हमें अपना ऐप्लिकेशन बनाना और डिप्लॉय करना होगा, ताकि परफ़ॉर्मेंस बेसलाइन बनाई जा सके, जिसकी तुलना हम बाद में नेटिव वर्शन से कर सकें.

1. प्रोजेक्ट बनाना

हम अपने ऐप्लिकेशन को start.spring.io से प्राप्त करके शुरू करेंगे:

curl https://start.spring.io/starter.zip -d dependencies=web \
           -d javaVersion=11 \
           -d bootVersion=2.6.4 -o io-native-starter.zip

इस स्टार्टर ऐप्लिकेशन में Spring बूट 2.6.4 का इस्तेमाल किया जाता है. यह वर्शन, लिखते समय स्प्रिंग-नेटिव प्रोजेक्ट का सबसे नया वर्शन है.

ध्यान दें कि GraalVM 21.0.3 के रिलीज़ होने के बाद, इस सैंपल के लिए भी Java 17 का इस्तेमाल किया जा सकता है. हम शामिल कॉन्फ़िगरेशन को कम करने के लिए, अब भी इस ट्यूटोरियल के लिए Java 11 का इस्तेमाल करेंगे.

जब हमें कमांड लाइन पर अपना ज़िप मिल जाता है, तो हम अपने प्रोजेक्ट के लिए एक सबडायरेक्ट्री बना सकते हैं और उसमें फ़ोल्डर को अनज़िप कर सकते हैं:

mkdir spring-native

cd spring-native

unzip ../io-native-starter.zip

2. कोड में बदलाव

प्रोजेक्ट शुरू होने के बाद, हम तुरंत इसमें लाइफ़ साइन से जुड़ेंगे और इसे चलाने के बाद स्प्रिंग नेटिव विज्ञापन की परफ़ॉर्मेंस दिखाएंगे.

इससे मैच करने के लिए, DemoApplication.java में बदलाव करें:

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.Duration;
import java.time.Instant;

@RestController
@SpringBootApplication
public class DemoApplication {
    private static Instant startTime;
    private static Instant readyTime;

    public static void main(String[] args) {
        startTime = Instant.now();
                SpringApplication.run(DemoApplication.class, args);
    }

    @GetMapping("/")
    public String index() {
        return "Time between start and ApplicationReadyEvent: "
                + Duration.between(startTime, readyTime).toMillis()
                + "ms";
    }

    @EventListener(ApplicationReadyEvent.class)
    public void ready() {
                readyTime = Instant.now();
    }
}

इस समय हमारा बेसलाइन ऐप्लिकेशन काम करने के लिए तैयार है. इसलिए, इससे पहले कि हम इसे किसी नेटिव ऐप्लिकेशन में बदलें, बिना किसी झिझक की कोई इमेज बनाएं और उसे स्थानीय तौर पर चलाएं, ताकि आपको स्टार्टअप समय के बारे में जानकारी मिल सके.

हमारी इमेज बनाने के लिए:

mvn spring-boot:build-image

बेसलाइन इमेज के साइज़ का अनुमान लगाने के लिए, docker images demo का भी इस्तेमाल किया जा सकता है: 6ecb403e9af1475e.png

हमारे ऐप्लिकेशन को चलाने के लिए:

docker run --rm -p 8080:8080 demo:0.0.1-SNAPSHOT

3. बेसलाइन ऐप्लिकेशन डिप्लॉय करें

अब हमारे पास अपना ऐप्लिकेशन है, तो हम उसे डिप्लॉय करेंगे और समय को नोट करेंगे. इसकी तुलना हम बाद में अपने स्थानीय ऐप्लिकेशन के स्टार्ट होने के समय से करेंगे.

आप जिस तरह का ऐप्लिकेशन बना रहे हैं उसके आधार पर आपकी चीज़ों को होस्ट करने वाले कई अलग-अलग विकल्प उपलब्ध हैं.

हालांकि, हमारा उदाहरण एक बहुत ही आसान और सटीक वेब ऐप्लिकेशन है, इसलिए हम चीज़ों को आसान रख सकते हैं और Cloud Run पर भरोसा कर सकते हैं.

अगर आपको अपने कंप्यूटर पर ही इसे फ़ॉलो करना है, तो पक्का करें कि आपने gcloud CLI टूल इंस्टॉल किया हो और उसे अपडेट किया हो.

अगर आप Cloud Shell पर हैं और आपको सोर्स डायरेक्ट्री में ये सभी काम करने हैं, तो इन्हें आसानी से इस्तेमाल किया जा सकता है:

gcloud run deploy

4. ऐप्लिकेशन का कॉन्फ़िगरेशन

1. Maven रिपॉज़िटरी को कॉन्फ़िगर करना

यह प्रोजेक्ट अब भी एक्सपेरिमेंट के तौर पर उपलब्ध है. इसलिए, हमें अपने ऐप्लिकेशन को कॉन्फ़िगर करना होगा, ताकि एक्सपेरिमेंट के तौर पर उपलब्ध आर्टफ़ैक्ट ढूंढे जा सकें. हालांकि, ऐसा करने के लिए, मेवन की सेंट्रल रिपॉज़िटरी (डेटा स्टोर करने की जगह) में उपलब्ध आर्टफ़ैक्ट उपलब्ध नहीं हैं.

इसके लिए, pom.xml में ये एलिमेंट जोड़े जाएंगे. अपनी पसंद के एडिटर में जाकर ऐसा किया जा सकता है.

हमारे pom में, नीचे दिए गए डेटा स्टोर करने की जगहें और प्लगिन रिपॉज़िटरी सेक्शन जोड़ें:

<repositories>
    <repository>
        <id>spring-release</id>
        <name>Spring release</name>
        <url>https://repo.spring.io/release</url>
    </repository>
</repositories>

<pluginRepositories>
    <pluginRepository>
        <id>spring-release</id>
        <name>Spring release</name>
        <url>https://repo.spring.io/release</url>
    </pluginRepository>
</pluginRepositories>

2. डिपेंडेंसी जोड़ना

इसके बाद, Spring-नेटिव डिपेंडेंसी जोड़ें, जो Spring ऐप्लिकेशन को नेटिव इमेज के तौर पर चलाने के लिए ज़रूरी है. ध्यान दें: अगर Gradle का इस्तेमाल किया जा रहा है, तो यह चरण ज़रूरी नहीं है

<dependencies>
    <!-- ... -->
    <dependency>
        <groupId>org.springframework.experimental</groupId>
        <artifactId>spring-native</artifactId>
        <version>0.11.2</version>
    </dependency>
</dependencies>

3. हमारे प्लगिन जोड़ना/चालू करना

अब मूल इमेज के साथ काम करने और फ़ुटप्रिंट में सुधार करने के लिए एओटी प्लगिन जोड़ें ( ज़्यादा पढ़ें):

<plugins>
    <!-- ... -->
    <plugin>
        <groupId>org.springframework.experimental</groupId>
        <artifactId>spring-aot-maven-plugin</artifactId>
        <version>0.11.2</version>
        <executions>
            <execution>
                <id>generate</id>
                <goals>
                    <goal>generate</goal>
                </goals>
            </execution>
        </executions>
    </plugin>
</plugins>

अब हम स्प्रिंग-बूट-मेवन-प्लग इन को अपडेट करेंगे, ताकि नेटिव इमेज की सुविधा चालू की जा सके. साथ ही, हमारी नेटिव इमेज बनाने के लिए पैकेटो बिल्डर का इस्तेमाल करें:

<plugins>
    <!-- ... -->
    <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <configuration>
            <image>
                <builder>paketobuildpacks/builder:tiny</builder>
                <env>
                    <BP_NATIVE_IMAGE>true</BP_NATIVE_IMAGE>
                </env>
            </image>
        </configuration>
    </plugin>    
</plugins>

ध्यान दें कि छोटा बिल्डर इमेज कई विकल्पों में से एक है. यह हमारे इस्तेमाल के उदाहरण के लिए अच्छा विकल्प है, क्योंकि इसमें बहुत कम अतिरिक्त लाइब्रेरी और सुविधाएं हैं, जो हमले की हमारी सरफ़ेस को कम करने में मदद करती हैं.

उदाहरण के लिए, अगर आपको ऐसा ऐप्लिकेशन बनाना है जिसमें कुछ सामान्य सी लाइब्रेरी का ऐक्सेस ज़रूरी हो या आपने अब तक अपने ऐप्लिकेशन की ज़रूरी शर्तों के बारे में नहीं बताया है, तो फ़ुल-बिल्डर टूल इस्तेमाल करना बेहतर होगा.

5. खास ऐप्लिकेशन बनाएं और चलाएं

यह सब हो जाने के बाद, हम अपनी इमेज बना सकेंगे और अपना स्थानीय, कंपाइल किया गया ऐप्लिकेशन चला सकेंगे.

बिल्ड को चलाने से पहले, इन बातों का ध्यान रखें:

  • इसमें सामान्य बिल्ड के मुकाबले ज़्यादा समय लगेगा (कुछ मिनट में) d420322893640701.png
  • बिल्ड प्रोसेस में बहुत ज़्यादा मेमोरी (कुछ गीगाबाइट (जीबी)) लग सकती है cda24e1eb11fdbea.png
  • इस बिल्ड प्रोसेस के लिए Docker डीमन को ऐक्सेस करना ज़रूरी है
  • इस उदाहरण में, हम मैन्युअल तरीके से इस प्रोसेस पर जा रहे हैं. हालांकि, बिल्ड के चरणों को भी कॉन्फ़िगर किया जा सकता है, ताकि नेटिव बिल्ड प्रोफ़ाइल अपने-आप ट्रिगर हो जाए.

हमारी इमेज बनाने के लिए:

mvn spring-boot:build-image

जब यह बन जाए, तो हम नेटिव ऐप्लिकेशन को काम करते हुए देखने के लिए तैयार हैं!

हमारे ऐप्लिकेशन को चलाने के लिए:

docker run --rm -p 8080:8080 demo:0.0.1-SNAPSHOT

इस समय हम स्थानीय अनुप्रयोग समीकरण के दोनों ओर देखने की अच्छी स्थिति में हैं.

हमने कंपाइल करते समय कुछ समय और अतिरिक्त मेमोरी का इस्तेमाल दिया है, लेकिन इसके बदले में हमें एक ऐसा ऐप्लिकेशन मिलता है जो ज़्यादा तेज़ी से शुरू हो सकता है, और बहुत कम मेमोरी का इस्तेमाल करता है (वर्कलोड के आधार पर).

अगर हम मूल इमेज के साइज़ की तुलना ओरिजनल इमेज से करने के लिए docker images demo चलाते हैं, तो हमें एक नाटकीय कमी दिख सकती है:

e667f65a011c1328.png

हमें यह भी ध्यान रखना चाहिए कि इस्तेमाल के ज़्यादा जटिल मामलों में, एओटी कंपाइलर को यह बताने के लिए कुछ और बदलाव करने ज़रूरी हैं कि आपका ऐप्लिकेशन रनटाइम के दौरान क्या करेगा. इस वजह से, अनुमान लगाए जा सकने वाले कुछ वर्कलोड (जैसे, बैच जॉब) इसके लिए सही साबित हो सकते हैं. हालांकि, अन्य तरह के वर्कलोड ज़्यादा काम के हो सकते हैं.

6. हमारे खास ऐप्लिकेशन का डिप्लॉयमेंट करना

अपने ऐप्लिकेशन को Cloud Run पर डिप्लॉय करने के लिए, हमें अपनी मूल इमेज को Artifact Registry जैसे पैकेज मैनेजर में ले जाना होगा.

1. Docker डेटा स्टोर करने की जगह को तैयार करना

इस प्रोसेस को शुरू करने के लिए, हम एक रिपॉज़िटरी बना सकते हैं:

gcloud artifacts repositories create native-image-docker-repo --repository-format=docker \
--location=us-central1 --description="Repository for our native images"

इसके बाद, हम यह पक्का करना चाहेंगे कि हम अपनी नई रजिस्ट्री को भेजने के लिए प्रमाणित हैं.

gcloud सीएलआई इस प्रक्रिया को कुछ हद तक आसान बना सकता है:

gcloud auth configure-docker us-central1-docker.pkg.dev

2. हमारी इमेज को Artifact Registry में शामिल करना

इसके बाद हम अपनी इमेज को टैग करेंगे:

export PROJECT_ID=$(gcloud config list --format 'value(core.project)')


docker tag  demo:0.0.1-SNAPSHOT \
us-central1-docker.pkg.dev/$PROJECT_ID/native-image-docker-repo/quickstart-image:tag2

इसके बाद, हम docker push का इस्तेमाल करके उसे Artifact Registry में भेज सकते हैं:

docker push us-central1-docker.pkg.dev/$PROJECT_ID/native-image-docker-repo/quickstart-image:tag2

3. Cloud Run में डिप्लॉय किया जा रहा है

अब हम Artifact Registry में सेव की गई इमेज को Cloud Run पर डिप्लॉय करने के लिए तैयार हैं:

gcloud run deploy --image us-central1-docker.pkg.dev/$PROJECT_ID/native-image-docker-repo/quickstart-image:tag2

हमने अपने ऐप्लिकेशन को मूल इमेज के तौर पर बनाया और डिप्लॉय किया है. इसलिए, हम इस बात पर भरोसा कर सकते हैं कि हमारा ऐप्लिकेशन, हमारे इन्फ़्रास्ट्रक्चर की लागत का बेहतरीन इस्तेमाल कर रहा है.

हमारे बेसलाइन ऐप्लिकेशन के स्टार्टअप समय की तुलना, इस नए स्थानीय ऐप्लिकेशन के स्टार्टअप समय से करें!

6dde63d35959b1bb.png

7. खास जानकारी/क्लीनअप

Google Cloud पर Spring Native ऐप्लिकेशन बनाने और उसे डिप्लॉय करने पर बधाई!

हमें उम्मीद है कि इस ट्यूटोरियल से आपको Spring Native के प्रोजेक्ट को समझने में मदद मिलेगी. साथ ही, इससे आपको आने वाले समय में आपकी ज़रूरतों को पूरा करने में मदद मिलेगी.

ज़रूरी नहीं: क्लीन अप और/या सेवा बंद करना

चाहे आपने इस कोडलैब के लिए Google Cloud प्रोजेक्ट बनाया हो या किसी मौजूदा कोड का फिर से इस्तेमाल किया हो, इसलिए ध्यान रखें कि हम जिन संसाधनों का इस्तेमाल करते हैं उन पर बेवजह कोई शुल्क न लगे.

हमारी बनाई हुई Cloud Run सेवाओं को मिटाया या बंद किया जा सकता है, हमारी होस्ट की गई इमेज को मिटाया जा सकता है या पूरा प्रोजेक्ट बंद किया जा सकता है.

8. अन्य संसाधन

वैसे तो Spring Native का प्रोजेक्ट अभी एक नया और प्रयोग के तौर पर शुरू किया गया प्रोजेक्ट है, लेकिन शुरुआत में लेने वाले लोगों को समस्याओं का हल करने और इसमें शामिल होने के लिए पहले से ही ढेर सारे अच्छे संसाधन मौजूद हैं:

अन्य संसाधन

नीचे कुछ ऑनलाइन संसाधन दिए गए हैं, जो इस ट्यूटोरियल के लिए काम के हो सकते हैं:

लाइसेंस

इस काम को क्रिएटिव कॉमंस एट्रिब्यूशन 2.0 जेनरिक लाइसेंस के तहत लाइसेंस मिला है.